	include	CDBIOS.INC

	;; asmx creates byte perfect versions
	
	;; SNASM68K and asm68k do not create byte perfect versions
	;; since they always convert ADD.L #0,D0 to ADDI.L #0,D0

	;; this is the PCM FD H register value (default $04 means ~16276Hz)
PCMFDH:				equ	($0400>>8)&$FF
	;; when using fixed base rate, what the fixed base rate it
FixedBaseRate:			equ	600
	;; the number of audio ticks per frame
	;; (default 16000, though 16276 should be used)
AudioTicksPerBaseRate:		equ	16000
	;; Engine data is the amount buffered from the CD at once
	;; required to allocate the min amount, tries for as much as the max
EngineDataMinSize:		equ	$10000
EngineDataMaxSize:		equ	$30000
	;; Engine wrap around buffer size is the max size an audio or image
	;; frame data can be (it copies wrap around here to make contigious
	;; data for each frame data)
EngineDataWrapAroundBufferSize:	equ	$04000

DisableInterruptsVint:		equ	$0300
	;; TODO what vector are we working on when using this value?
DisableInterruptsTodo:		equ	$0700
InterruptVectorTodo:		equ	$5F7E

	;; heap entry info

HeapEntryLength:		equ	$10
	
HeapEntryOffsetZero:		equ	$00
HeapEntryOffsetOne:		equ	$01
HeapEntryOffsetUsage:		equ	$02
HeapEntryOffsetPreviousEntry:	equ	$04
HeapEntryOffsetNextEntry:	equ	$08
HeapEntryOffsetZeros:		equ	$0C

HeapEntryZeroAll:		equ	'H'

HeapEntryOneNotEnd:		equ	'B'
HeapEntryOneEnd:		equ	'E'

HeapEntryUsageAvailable:	equ	$00
HeapEntryUsageUsed:		equ	$01
HeapEntryUsageEnd:		equ	$02

WordRAMStart:	equ	$000C0000

	;; shortcuts for link a6

LocalVar04:	equ	-$04
LocalVar08:	equ	-$08
LocalVar0C:	equ	-$0C
LocalVar10:	equ	-$10

	;; shortcuts for args linked

Arg00Link:	equ	$8
Arg02Link:	equ	Arg00Link+$02
Arg04Link:	equ	Arg00Link+$04
Arg06Link:	equ	Arg00Link+$06
Arg08Link:	equ	Arg00Link+$08
Arg0ALink:	equ	Arg00Link+$0A
Arg0ELink:	equ	Arg00Link+$0E
Arg12Link:	equ	Arg00Link+$12
Arg16Link:	equ	Arg00Link+$16
Arg1ALink:	equ	Arg00Link+$1A
Arg1ELink:	equ	Arg00Link+$1E
Arg22Link:	equ	Arg00Link+$22

	;; shortcuts for args not linked

Arg00NoLink:	equ	$4
Arg04NoLink:	equ	Arg00NoLink+$04

	
SubPlayStart:	
	;; to start Cinepak, JSR to to this location
	BRA	CinepakStart
Ticks:
	dc.l	$00000000
HeapStart:
	dc.l	$00010000
HeapLength:
	dc.l	$00060000
CDDecoderNumberOfSectors:
	;; zero means no more sectors
	;; positive means something TODO
	;; negative means something TODO
	dc.l	$00000000
CDDecoderDestination:	
	dc.l	$00000000
CDDecoderSectorStart:
	dc.l	$00000000
EngineDataPointer:	
	dc.l	$00000000
VRAMTileDataStart:
	dc.l	$00000000
VRAMTileMapStart:	
	dc.l	$00000000
FilmX:	
	dc.l	$00000000
FilmY:
	dc.l	$00000000
FilmTilePriority:
	dc.l	$00000000
CinepakVintUserCall:
	dc.l	$00000000
CDCReadDataNotPresent:
	;; $00000000 no problems (TODO...)
	;; $FFFFFFFF data not present or timeout
	dc.l	$00000000
STABAudioAndVideoInfo:
	;;  0 has both audio and video
	;;  1 has no audio (may or may not have video)
	;; -1 has audio and no video
	dc.l	$00000000
FilmFXDataOffset:	
	;; used in FilmOpen, FilmTask, FilmClose for as a pointer to
	;; data farther down (these bytes appear to be unused)
	dc.l	$00000000
	dc.l	$00000000
FILMHeaderLength:
	dc.l    $00000000
FDSCStartAddress:	
	dc.l	$00000000
STABStartAddress:	
	dc.l	$00000000
FILMHeaderHeapAddress:	
	dc.l	$00000000
EngineData:	
EngineDataBufferStart:	
	dc.l	$00000000
EngineDataBufferSize:
	;; (does not include EngineDataWrapAroundBufferSize)
	dc.l	$00000000
Label0060:	
	dc.l	$00000000
EngineReadDataEnd:
	dc.l	$00000000
EngineSectorStart:	
	dc.l	$00000000
EngineDataUnused:	
	;; appears to be unused, cleared by InitEngine
	dc.l	$00000000
AudioData:
Audio4kPlayedTicks:	
	;; $FFFFFFFF is not playing (default during init)
	;; any other value is the number of 4k blocks played
	dc.l	$00000000
Audio4kCopiedTicks:	
	dc.l	$00000000
AudioDataBufferStart:	
	;; location of start of data within audio buffer
	dc.l	$00000000
AudioDataBufferSize:
	;; size of remaining data in audio buffer
	dc.l	$00000000
STABIndex:	
	;; when $FFFFFFFF flag that no more STAB entries
	dc.l	$00000000
	;; TOOD unused space?
	dc.w	$0000,$0000,$0000,$0000,$0000,$0000
	dc.w	$0000,$0000,$0000,$0000,$0000,$0000,$0000,$0000
	dc.w	$0000,$0000,$0000,$0000,$0000,$0000,$0000,$0000
	dc.w	$0000,$0000,$0000,$0000,$0000,$0000,$0000,$0000
	dc.w	$0000,$0000,$0000,$0000,$0000,$0000,$0000,$0000
	dc.w	$0000,$0000,$0000,$0000,$0000,$0000,$0000,$0000
	dc.w	$0000,$0000,$0000,$0000
	dc.b	"CINEPAK VER 1.3",$00

CinepakStart:
	;; FX args:$00.w cinepak action: 0 (open), 1 (task), else (close)
	;; FX args:$02.l sector start of cinepak file
	;; FX args:$06.l saved to HeapStart
	;; FX args:$0A.l saved to HeapLength
	;; FX args:$0E.l saved to FilmY
	;; FX args:$12.l saved to FilmX
	;; FX args:$16.l saved to VRAMTileMapStart
	;; FX args:$1A.l saved to VRAMTileDataStart
	;; FX args:$1E.l saved to FilmTilePriority
	;; FX args:$22.l saved to CinepakVintUserCall
	;;    (0 means no call, nonzero means jsr to this address)
	TST.W    $4(A7)
	BNE.S    CinepakActionNonZero

	LINK     A6,#$0
	MOVEM.L  D1-D7/A0-A5,-(A7)
	;; clear to number of sectors (since nothing is being read)
	LEA      CDDecoderNumberOfSectors(PC),A0
	CLR.L    (A0)
	LEA      HeapLength(PC),A0
	MOVE.L   Arg0ALink(A6),(A0)
	LEA      HeapStart(PC),A0
	MOVE.L   Arg06Link(A6),(A0)
	LEA      VRAMTileDataStart(PC),A0
	;; VRAMTileDataStart
	MOVE.L   Arg1ALink(A6),(A0)+
	;; VRAMTileMapStart
	MOVE.L   Arg16Link(A6),(A0)+
	;; FilmY
	MOVE.L   Arg12Link(A6),(A0)+
	;; FilmX
	MOVE.L   Arg0ELink(A6),(A0)+
	;; FilmTilePriority
	MOVE.L   Arg1ELink(A6),(A0)+
	;; CinepakVintUserCall
	MOVE.L   Arg22Link(A6),(A0)+
	;; set 1M mode
	MOVEA.L  #$FF8000,A5
	ORI.W    #$0004,$2(A5)
	;; set vint for cinepak
	JSR      SetVintCinepak(PC)
	;; push args, call FilmOpen, and pop args
	MOVE.L   Arg02Link(A6),-(A7)
	JSR      FilmOpen(PC)
	ADDQ.W   #4,A7
	;; restore regs saved on stack
	MOVEM.L  (A7)+,D1-D7/A0-A5
	UNLK     A6
	;; sets the flags based on the FilmOpen return code
	TST.L    D0
	RTS

CinepakActionNonZero:	
	;; FX args:$00.w cinepak action: 0 (open), 1 (task), else (close)
	LINK     A6,#$0
	MOVEM.L  D1-D7/A0-A5,-(A7)
	MOVE.W   Arg00Link(A6),D0
	SUBQ.W   #1,D0
	BNE.S    CinepakStop
	JSR      FilmTask(PC)
	BRA.S    EndCinepakActionNonZero

CinepakStop:	
	JSR      FilmClose(PC)
	JSR      RestoreVint(PC)
	MOVEQ    #$0,D0

EndCinepakActionNonZero:	
	MOVEM.L  (A7)+,D1-D7/A0-A5
	UNLK     A6
	;; sets the flags based on the FilmTask/FilmClose return code
	TST.L    D0
	RTS

	;; Unused function (doesn't look like data, but is a weird function)
	MOVE.W   $0,D0
UnusedFunctionLoop:	
	ADDQ.W   #1,$0
	CMP.W    $0,D0
	BEQ.S    UnusedFunctionLoop
	MOVE.W   D0,$0
	CLR.L    $8
	CLR.L    $C
	CLR.L    $10
	CLR.L    $14
	CLR.L    $18
	CLR.L    $1C
	CLR.L    $20
	CLR.L    $24
	MOVE.L   #$208008,$80
	RTS

OriginalVint:	
	dc.l	$00000000

SetVintCinepak:	
	LEA      OriginalVint(PC),A0
	MOVE.L   _USERCALL2+2,(A0)
	LEA      CinepakVint(PC),A0
	;; save SR, turn off USERCALL2, set new vint, restore SR
	MOVE     SR,-(A7)
	ORI      #DisableInterruptsVint,SR
	MOVE.L   A0,_USERCALL2+2
	MOVE     (A7)+,SR
	RTS

RestoreVint:
	MOVEA.L  OriginalVint(PC),A0
	;; save SR, turn off USERCALL2, set old vint, restore SR
	MOVE     SR,-(A7)
	ORI      #DisableInterruptsVint,SR
	MOVE.L   A0,$5F36
	MOVE     (A7)+,SR
	RTS

CinepakVint:	
	MOVEM.L  D0/A0,-(A7)
	LEA      Ticks(PC),A0
	ADDQ.L   #1,(A0)
	JSR      CinepakVintSub(PC)
	MOVE.L   CinepakVintUserCall(PC),D0
	BEQ.S    CinepakVintSkipUserCall
	MOVEA.L  D0,A0
	JSR      (A0)
CinepakVintSkipUserCall:	
	MOVEM.L  (A7)+,D0/A0
        RTS

	;; FX args$08.l: start of area to store uncompressed data
	;; FX args$0C.l: start of frame data
DecodeFrame:
	LINK     A6,#$0
	MOVEM.L  D0-D7/A0-A5,-(A7)
	MOVEA.L  Arg00Link(A6),A0
	MOVEA.L  Arg04Link(A6),A1
	;; require 'SM' characters
	CMPI.W   #'SM',(A1)
	BNE      EndDecodeFrame
	;; get width in number of tiles
	MOVE.W   $8(A1),D0
	EXT.L    D0
	;; get width in number of tiles
	MOVE.W   $A(A1),D7
	EXT.L    D7
	;; get number of total tiles (and save in d7)
	MULU.W   D0,D7
	MOVE.L   D7,D6
	;; multiply number of tiles by 2
	ADD.L    D6,D6
	;; add 31 to the number of tiles
	ADDI.W   #$1F,D6
	;; divide by 32
	LSR.W    #5,D6
	;; multiply by 4
	LSL.W    #2,D6
	;; this 31 add and shifting order makes d6 be aligned on a
	;; long word boundary

	;; 12 byte header always copied
	MOVE.W   #$C,D0
	;; get the number of palettes
	MOVE.W   $2(A1),D1
	ANDI.W   #$3,D1
	BEQ.S    NoPaletteSelectTable
	;; add in the calculated palette select table
	ADD.W    D6,D0
NoPaletteSelectTable:	
	;; add 1 to get number of palettes
	ADDQ.W   #1,D1
	;; multiply by 32 since each palette is 32 bytes
	LSL.W    #5,D1
	;; add palette length to copy count
	ADD.W    D1,D0
	;; divide byte count by 4 (to get count in long words)
	LSR.W    #2,D0
	;; subtract one to account for using dbf loop
	SUBQ.W   #1,D0
	;; copy header, palettes, and possible palette selection table
DecodeFrameHeaderCopy:
	MOVE.L   (A1)+,(A0)+
	DBF      D0,DecodeFrameHeaderCopy
	;; get table length
	MOVE.L   (A1)+,D0
	;; save start of 32 bit vector codebook
	MOVEA.L  A1,A2
	;; increment to next table
	ADDA.L   D0,A1
	;; get table length
	MOVE.L   (A1)+,D0
	;; save start of 16 bit vector codebook
	MOVEA.L  A1,A3
	;; increment to next table
	ADDA.L   D0,A1
	;; save start of tile encoding type table
	MOVEA.L  A1,A4
	;; increment to next table
	ADDA.L   D6,A1
	
	;; d3 indicates current tile count saved in d5 for tile encoding type
	MOVEQ    #$1,D3
	;; account for dbf loop on the total tile count
	SUBQ.W   #1,D7
DecodeTilesLoop:
	;; subtract tile from number of tiles left in d5
	SUBQ.W   #1,D3
	;; if non-zero, we already have more saved in d5
	BNE.S    MoreTilesInEncodingTypeInfo
	;; get another 16 tiles of 2 bit tile encoding type info
	MOVE.L   (A4)+,D5
	MOVEQ    #$10,D3
MoreTilesInEncodingTypeInfo:	
	;; shift d5 by 1 and check carry
	ADD.L    D5,D5
	BCS.S    EncodingType1X
	;; encoding type is $
	;; shift d5 again by 1 and check carry
	ADD.L    D5,D5
	BCS.S    EncodingType01

	;; encoding type is 00
	;; no change, skip 32 bytes for tile
	;; due to 1M switching, these means same tile as two frames ago
	LEA      $20(A0),A0
	;; if more tiles process another tile
	DBF      D7,DecodeTilesLoop
	BRA      EndDecodeFrame

EncodingType01:	
	;; copy uncompressed 32 bytes straight through
	MOVE.L   (A1)+,(A0)+
	MOVE.L   (A1)+,(A0)+
	MOVE.L   (A1)+,(A0)+
	MOVE.L   (A1)+,(A0)+
	MOVE.L   (A1)+,(A0)+
	MOVE.L   (A1)+,(A0)+
	MOVE.L   (A1)+,(A0)+
	MOVE.L   (A1)+,(A0)+
	;; if more tiles process another tile
	DBF      D7,DecodeTilesLoop
	BRA      EndDecodeFrame

EncodingType1X:	
	;; shift d5 again by 1 and check carry
	ADD.L    D5,D5
	BCS.S    EncodingType11

	;; loop 4 times (so each iteration handles 8 bytes)
	MOVEQ    #$3,D4
EncodingType10Loop:	
	;; get vector index 0 and multiply by 4 (since each entry is 4 bytes)
	MOVEQ    #$0,D0
	MOVE.B   (A1)+,D0
	LSL.W    #2,D0
	;; get vector 0 value from 32 bit vector codebook
	MOVE.L   $0(A2,D0.W),D0
	;; get vector index 1 and multiply by 4 (since each entry is 4 bytes)
	MOVEQ    #$0,D1
	MOVE.B   (A1)+,D1
	LSL.W    #2,D1
	;; get vector 1 value from 32 bit vector codebook
	MOVE.L   $0(A2,D1.W),D1
	;;  swap so high words are accesible using move.w
	SWAP     D0
	SWAP     D1
	;; store high word of vector 0 value
	MOVE.W   D0,(A0)+
	;; store high word of vector 1 value
	MOVE.W   D1,(A0)+
	;;  swap again so low words are accesible using move.w
	SWAP     D0
	SWAP     D1
	;; store low word of vector 0 value
	MOVE.W   D0,(A0)+
	;; store low word of vector 1 value
	MOVE.W   D1,(A0)+
	;; loop for each 8 byte value
	DBF      D4,EncodingType10Loop
	;; if more tiles process another tile
	DBF      D7,DecodeTilesLoop
	BRA.S    EndDecodeFrame

EncodingType11:	
	;; loop 4 times (so each iteration handles 8 bytes)
	MOVEQ    #$3,D4
EncodingType11Loop:	
	;; results in v0[1] v1[1] v2[1] v3[1] v0[0] v1[0] v2[0] v3[0]
	;; this is done through LSL and MOVE.B operations on d1 and d2
	;; get vector index 0 and multiply by 2 (since each entry is 2 bytes)
	MOVEQ    #$0,D0
	MOVE.B   (A1)+,D0
	ADD.W    D0,D0
	;; get vector 0 value from 16 bit vector codebook
	MOVE.W   $0(A3,D0.W),D1
	MOVE.B   D1,D2
	LSL.L    #8,D1
	LSL.L    #8,D2
	;; get vector index 1 and multiply by 2 (since each entry is 2 bytes)
	MOVEQ    #$0,D0
	MOVE.B   (A1)+,D0
	ADD.W    D0,D0
	;; get vector 1 value from 16 bit vector codebook
	MOVE.W   $0(A3,D0.W),D1
	MOVE.B   D1,D2
	LSL.L    #8,D1
	LSL.L    #8,D2
	;; get vector index 2 and multiply by 2 (since each entry is 2 bytes)
	MOVEQ    #$0,D0
	MOVE.B   (A1)+,D0
	ADD.W    D0,D0
	;; get vector 2 value from 16 bit vector codebook
	MOVE.W   $0(A3,D0.W),D1
	MOVE.B   D1,D2
	LSL.L    #8,D2
	;; get vector index 2 and multiply by 2 (since each entry is 2 bytes)
	MOVEQ    #$0,D0
	MOVE.B   (A1)+,D0
	ADD.W    D0,D0
	;; get vector 3 value from 16 bit vector codebook
	MOVE.W   $0(A3,D0.W),D0
	MOVE.B   D0,D2
	LSR.W    #8,D0
	MOVE.B   D0,D1
	MOVE.L   D1,(A0)+
	MOVE.L   D2,(A0)+
	;; loop for each 8 byte value
	DBF      D4,EncodingType11Loop
	;; if more tiles process another tile
	DBF      D7,DecodeTilesLoop

EndDecodeFrame:	
	MOVEM.L  (A7)+,D0-D7/A0-A5
	UNLK     A6
	MOVEA.L  (A7)+,A0
	ADDQ.W   #8,A7
	JMP      (A0)

	
AudioInitSub:	
	;; pcm memory start
	MOVEA.L  #$FF0000,A0
	;; sound off all channels
	MOVE.B   #$FF,$11(A0)
	MOVEQ    #$14,D0
AudioInitSubChAllWait:	
	DBF      D0,AudioInitSubChAllWait

	;; modify channel 1
	MOVE.B   #$C0,$F(A0)
	MOVEQ    #$14,D0
AudioInitSubCh1Wait0:	
	DBF      D0,AudioInitSubCh1Wait0
	;; envelope (affects amplitude)
	MOVE.B   #$B4,$1(A0)
	MOVEQ    #$14,D0
AudioInitSubCh1Wait1:	
	DBF      D0,AudioInitSubCh1Wait1
	;; set both left and right to equal panning
	MOVE.B   #$FF,$3(A0)
	MOVEQ    #$14,D0
AudioInitSubCh1Wait2:	
	DBF      D0,AudioInitSubCh1Wait2
	;; set sampling (high) to 1/2 base sampling frequency
	;; which is (12.5MHz/384)*0.5
	MOVE.B   #PCMFDH,$7(A0)
	MOVEQ    #$14,D0
AudioInitSubCh1Wait3:
	DBF      D0,AudioInitSubCh1Wait3
	;; set sampling to (low) 1/2 base sampling frequency
	CLR.B    $5(A0)
	MOVEQ    #$14,D0
AudioInitSubCh1Wait4:	
	DBF      D0,AudioInitSubCh1Wait4
	;; set stop address (high)
	CLR.B    $B(A0)
	MOVEQ    #$14,D0
AudioInitSubCh1Wait5:	
	DBF      D0,AudioInitSubCh1Wait5
	;; set stop address (low)
	CLR.B    $9(A0)
	MOVEQ    #$14,D0
AudioInitSubCh1Wait6:
	DBF      D0,AudioInitSubCh1Wait6
	CLR.B    $D(A0)
	MOVEQ    #$14,D0
AudioInitSubCh1Wait7:
	DBF      D0,AudioInitSubCh1Wait7

	;; modify channel 2
	MOVE.B   #$C1,$F(A0)
	MOVEQ    #$14,D0
AudioInitSubCh2Wait0:	
	DBF      D0,AudioInitSubCh2Wait0
	;; envelope (affects amplitude)
	MOVE.B   #$FF,$1(A0)
	MOVEQ    #$14,D0
AudioInitSubCh2Wait1:	
	DBF      D0,AudioInitSubCh2Wait1
	;; set both left and right to equal panning
	MOVE.B   #$FF,$3(A0)
	MOVEQ    #$14,D0
AudioInitSubCh2Wait2:	
	DBF      D0,AudioInitSubCh2Wait2
	;; set sampling (high) to 1/2 base sampling frequency
	;; which is (12.5MHz/384)*0.5
	MOVE.B   #PCMFDH,$7(A0)
	MOVEQ    #$14,D0
AudioInitSubCh2Wait3:
	DBF      D0,AudioInitSubCh2Wait3
	;; set sampling to (low) 1/2 base sampling frequency
	CLR.B    $5(A0)
	MOVEQ    #$14,D0
AudioInitSubCh2Wait4:	
	DBF      D0,AudioInitSubCh2Wait4
	;; set stop address (high)
	CLR.B    $B(A0)
	MOVEQ    #$14,D0
AudioInitSubCh2Wait5:	
	DBF      D0,AudioInitSubCh2Wait5
	;; set stop address (low)
	CLR.B    $9(A0)
	MOVEQ    #$14,D0
AudioInitSubCh2Wait6:
	DBF      D0,AudioInitSubCh2Wait6
	CLR.B    $D(A0)
	MOVEQ    #$14,D0
AudioInitSubCh2Wait7:
	DBF      D0,AudioInitSubCh2Wait7

	;; modify channel 3
	MOVE.B   #$C2,$F(A0)
	MOVEQ    #$14,D0
AudioInitSubCh3Wait0:	
	DBF      D0,AudioInitSubCh3Wait0
	;; envelope (affects amplitude)
	MOVE.B   #$FF,$1(A0)
	MOVEQ    #$14,D0
AudioInitSubCh3Wait1:	
	DBF      D0,AudioInitSubCh3Wait1
	;; set both left and right to equal panning
	MOVE.B   #$FF,$3(A0)
	MOVEQ    #$14,D0
AudioInitSubCh3Wait2:	
	DBF      D0,AudioInitSubCh3Wait2
	;; set sampling (high) to 1/2 base sampling frequency
	;; which is (12.5MHz/384)*0.5
	MOVE.B   #PCMFDH,$7(A0)
	MOVEQ    #$14,D0
AudioInitSubCh3Wait3:
	DBF      D0,AudioInitSubCh3Wait3
	;; set sampling to (low) 1/2 base sampling frequency
	CLR.B    $5(A0)
	MOVEQ    #$14,D0
AudioInitSubCh3Wait4:	
	DBF      D0,AudioInitSubCh3Wait4
	;; set stop address (high)
	CLR.B    $B(A0)
	MOVEQ    #$14,D0
AudioInitSubCh3Wait5:	
	DBF      D0,AudioInitSubCh3Wait5
	;; set stop address (low)
	CLR.B    $9(A0)
	MOVEQ    #$14,D0
AudioInitSubCh3Wait6:
	DBF      D0,AudioInitSubCh3Wait6
	CLR.B    $D(A0)
	MOVEQ    #$14,D0
AudioInitSubCh3Wait7:
	DBF      D0,AudioInitSubCh3Wait7

	;; modify channel 4
	MOVE.B   #$C3,$F(A0)
	MOVEQ    #$14,D0
AudioInitSubCh4Wait0:	
	DBF      D0,AudioInitSubCh4Wait0
	;; envelope (affects amplitude)
	MOVE.B   #$FF,$1(A0)
	MOVEQ    #$14,D0
AudioInitSubCh4Wait1:	
	DBF      D0,AudioInitSubCh4Wait1
	;; set both left and right to equal panning
	MOVE.B   #$FF,$3(A0)
	MOVEQ    #$14,D0
AudioInitSubCh4Wait2:	
	DBF      D0,AudioInitSubCh4Wait2
	;; set sampling (high) to 1/2 base sampling frequency
	;; which is (12.5MHz/384)*0.5
	MOVE.B   #PCMFDH,$7(A0)
	MOVEQ    #$14,D0
AudioInitSubCh4Wait3:
	DBF      D0,AudioInitSubCh4Wait3
	;; set sampling to (low) 1/2 base sampling frequency
	CLR.B    $5(A0)
	MOVEQ    #$14,D0
AudioInitSubCh4Wait4:	
	DBF      D0,AudioInitSubCh4Wait4
	;; set stop address (high)
	CLR.B    $B(A0)
	MOVEQ    #$14,D0
AudioInitSubCh4Wait5:	
	DBF      D0,AudioInitSubCh4Wait5
	;; set stop address (low)
	CLR.B    $9(A0)
	MOVEQ    #$14,D0
AudioInitSubCh4Wait6:
	DBF      D0,AudioInitSubCh4Wait6
	CLR.B    $D(A0)
	MOVEQ    #$14,D0
AudioInitSubCh4Wait7:
	DBF      D0,AudioInitSubCh4Wait7

	;; modify channel 5
	MOVE.B   #$C4,$F(A0)
	MOVEQ    #$14,D0
AudioInitSubCh5Wait0:	
	DBF      D0,AudioInitSubCh5Wait0
	;; envelope (affects amplitude)
	MOVE.B   #$FF,$1(A0)
	MOVEQ    #$14,D0
AudioInitSubCh5Wait1:	
	DBF      D0,AudioInitSubCh5Wait1
	;; set both left and right to equal panning
	MOVE.B   #$FF,$3(A0)
	MOVEQ    #$14,D0
AudioInitSubCh5Wait2:	
	DBF      D0,AudioInitSubCh5Wait2
	;; set sampling (high) to 1/2 base sampling frequency
	;; which is (12.5MHz/384)*0.5
	MOVE.B   #PCMFDH,$7(A0)
	MOVEQ    #$14,D0
AudioInitSubCh5Wait3:
	DBF      D0,AudioInitSubCh5Wait3
	;; set sampling to (low) 1/2 base sampling frequency
	CLR.B    $5(A0)
	MOVEQ    #$14,D0
AudioInitSubCh5Wait4:	
	DBF      D0,AudioInitSubCh5Wait4
	;; set stop address (high)
	CLR.B    $B(A0)
	MOVEQ    #$14,D0
AudioInitSubCh5Wait5:	
	DBF      D0,AudioInitSubCh5Wait5
	;; set stop address (low)
	CLR.B    $9(A0)
	MOVEQ    #$14,D0
AudioInitSubCh5Wait6:
	DBF      D0,AudioInitSubCh5Wait6
	CLR.B    $D(A0)
	MOVEQ    #$14,D0
AudioInitSubCh5Wait7:
	DBF      D0,AudioInitSubCh5Wait7

	;; modify channel 6
	MOVE.B   #$C5,$F(A0)
	MOVEQ    #$14,D0
AudioInitSubCh6Wait0:	
	DBF      D0,AudioInitSubCh6Wait0
	;; envelope (affects amplitude)
	MOVE.B   #$FF,$1(A0)
	MOVEQ    #$14,D0
AudioInitSubCh6Wait1:	
	DBF      D0,AudioInitSubCh6Wait1
	;; set both left and right to equal panning
	MOVE.B   #$FF,$3(A0)
	MOVEQ    #$14,D0
AudioInitSubCh6Wait2:	
	DBF      D0,AudioInitSubCh6Wait2
	;; set sampling (high) to 1/2 base sampling frequency
	;; which is (12.5MHz/384)*0.5
	MOVE.B   #PCMFDH,$7(A0)
	MOVEQ    #$14,D0
AudioInitSubCh6Wait3:
	DBF      D0,AudioInitSubCh6Wait3
	;; set sampling to (low) 1/2 base sampling frequency
	CLR.B    $5(A0)
	MOVEQ    #$14,D0
AudioInitSubCh6Wait4:	
	DBF      D0,AudioInitSubCh6Wait4
	;; set stop address (high)
	CLR.B    $B(A0)
	MOVEQ    #$14,D0
AudioInitSubCh6Wait5:	
	DBF      D0,AudioInitSubCh6Wait5
	;; set stop address (low)
	CLR.B    $9(A0)
	MOVEQ    #$14,D0
AudioInitSubCh6Wait6:
	DBF      D0,AudioInitSubCh6Wait6
	CLR.B    $D(A0)
	MOVEQ    #$14,D0
AudioInitSubCh6Wait7:
	DBF      D0,AudioInitSubCh6Wait7

	;; modify channel 7
	MOVE.B   #$C6,$F(A0)
	MOVEQ    #$14,D0
AudioInitSubCh7Wait0:	
	DBF      D0,AudioInitSubCh7Wait0
	;; envelope (affects amplitude)
	MOVE.B   #$FF,$1(A0)
	MOVEQ    #$14,D0
AudioInitSubCh7Wait1:	
	DBF      D0,AudioInitSubCh7Wait1
	;; set both left and right to equal panning
	MOVE.B   #$FF,$3(A0)
	MOVEQ    #$14,D0
AudioInitSubCh7Wait2:	
	DBF      D0,AudioInitSubCh7Wait2
	;; set sampling (high) to 1/2 base sampling frequency
	;; which is (12.5MHz/384)*0.5
	MOVE.B   #PCMFDH,$7(A0)
	MOVEQ    #$14,D0
AudioInitSubCh7Wait3:
	DBF      D0,AudioInitSubCh7Wait3
	;; set sampling to (low) 1/2 base sampling frequency
	CLR.B    $5(A0)
	MOVEQ    #$14,D0
AudioInitSubCh7Wait4:	
	DBF      D0,AudioInitSubCh7Wait4
	;; set stop address (high)
	CLR.B    $B(A0)
	MOVEQ    #$14,D0
AudioInitSubCh7Wait5:	
	DBF      D0,AudioInitSubCh7Wait5
	;; set stop address (low)
	CLR.B    $9(A0)
	MOVEQ    #$14,D0
AudioInitSubCh7Wait6:
	DBF      D0,AudioInitSubCh7Wait6
	CLR.B    $D(A0)
	MOVEQ    #$14,D0
AudioInitSubCh7Wait7:
	DBF      D0,AudioInitSubCh7Wait7

	;; modify channel 8
	MOVE.B   #$C7,$F(A0)
	MOVEQ    #$14,D0
AudioInitSubCh8Wait0:	
	DBF      D0,AudioInitSubCh8Wait0
	;; envelope (affects amplitude)
	MOVE.B   #$FF,$1(A0)
	MOVEQ    #$14,D0
AudioInitSubCh8Wait1:	
	DBF      D0,AudioInitSubCh8Wait1
	;; set both left and right to equal panning
	MOVE.B   #$FF,$3(A0)
	MOVEQ    #$14,D0
AudioInitSubCh8Wait2:	
	DBF      D0,AudioInitSubCh8Wait2
	;; set sampling (high) to 1/2 base sampling frequency
	;; which is (12.5MHz/384)*0.5
	MOVE.B   #PCMFDH,$7(A0)
	MOVEQ    #$14,D0
AudioInitSubCh8Wait3:
	DBF      D0,AudioInitSubCh8Wait3
	;; set sampling to (low) 1/2 base sampling frequency
	CLR.B    $5(A0)
	MOVEQ    #$14,D0
AudioInitSubCh8Wait4:	
	DBF      D0,AudioInitSubCh8Wait4
	;; set stop address (high)
	CLR.B    $B(A0)
	MOVEQ    #$14,D0
AudioInitSubCh8Wait5:	
	DBF      D0,AudioInitSubCh8Wait5
	;; set stop address (low)
	CLR.B    $9(A0)
	MOVEQ    #$14,D0
AudioInitSubCh8Wait6:
	DBF      D0,AudioInitSubCh8Wait6
	CLR.B    $D(A0)
	MOVEQ    #$14,D0
AudioInitSubCh8Wait7:
	DBF      D0,AudioInitSubCh8Wait7
	
	;; set wave memory address to bank 0 (RAMC2B)
	MOVE.W   #$88,D0
	MOVE.B   D0,$F(A0)
	MOVEQ    #$14,D0
AudioInitSubWait:	
	DBF      D0,AudioInitSubWait
	;; set initial wave data to stop data value
	MOVE.B   #$FF,$2001(A0)
	MOVE.B   #$FF,$2003(A0)
	RTS

AudioStart:
	;; pcm memory start
	MOVEA.L  #$FF0000,A0
	;; set wave memory address to bank 0 (RAMC1B)
	MOVE.B   #$80,$F(A0)
	MOVEQ    #$14,D0
AudioStartWait:
	DBF      D0,AudioStartWait
	;; sound on channel 1 only
	MOVE.B   #$FE,$11(A0)
	RTS

AudioStopSub:	
	;; pcm memory start
	MOVEA.L  #$FF0000,A0
	;; sound off all channels
	MOVE.B   #$FF,$11(A0)
	RTS


GetWaveAddressCh1:	
	;; FX destroys a0.l, d1.b
	;; FX returns d0.w to wave address ch1
	;; pcm memory start
	MOVEA.L  #$FF0000,A0
	LEA      $21(A0),A0
	MOVEQ    #$14,D0
GetWaveAddressCh1Wait0:	
	DBF      D0,GetWaveAddressCh1Wait0
	;; read wave ram address ch1 low (read twice to make sure it settles)
	MOVE.B   (A0),D1
	MOVE.B   (A0),D1
	ADDQ.W   #2,A0
	MOVEQ    #$14,D0
GetWaveAddressCh1Wait1:	
	DBF      D0,GetWaveAddressCh1Wait1
	MOVEQ    #$0,D0
	;; read wave ram address ch1 high (read twice to make sure it settles)
	MOVE.B   (A0),D0
	MOVE.B   (A0),D0
	;; now assemble low and high addresses into d0
	LSL.W    #8,D0
	MOVE.B   D1,D0
	RTS

Audio4kCopy:	
	;; FX args$00.l wave data to copy
	;; FX args$04.l wave ram upper bits
	;; pcm memory start
	MOVEA.L  #$FF0000,A0
	;; set desired wave bank for wave RAM writes
	MOVE.L   Arg04NoLink(A7),D0
	ORI.W    #$80,D0
	MOVE.B   D0,$F(A0)
	MOVEQ    #$14,D0
Audio4kCopyChWait1:	
	DBF      D0,Audio4kCopyChWait1
	;; TODO setting $00? or $FF?
	MOVE.B   D0,$F(A0)
	MOVEQ    #$14,D0
Audio4kCopyChWait2:	
	DBF      D0,Audio4kCopyChWait2
	;; start of wave RAM address
	LEA      $2001(A0),A0
	;; start of wave data in audio buffer
	MOVEA.L  Arg00NoLink(A7),A1
	;; copy 4k into wave RAM (note: only use odd wave RAM addresses)
	MOVE.W   #$1FF,D3
Audio4kCopyChLoop:	
	MOVE.B   (A1)+,(A0)
	ADDQ.W   #2,A0
	MOVE.B   (A1)+,(A0)
	ADDQ.W   #2,A0
	MOVE.B   (A1)+,(A0)
	ADDQ.W   #2,A0
	MOVE.B   (A1)+,(A0)
	ADDQ.W   #2,A0
	MOVE.B   (A1)+,(A0)
	ADDQ.W   #2,A0
	MOVE.B   (A1)+,(A0)
	ADDQ.W   #2,A0
	MOVE.B   (A1)+,(A0)
	ADDQ.W   #2,A0
	MOVE.B   (A1)+,(A0)
	ADDQ.W   #2,A0
	DBF      D3,Audio4kCopyChLoop
	RTS

OriginalTodoVector:	
	dc.l $00000000

SetTodoVectorCinepak:	
	;; depending on the revision may be an unused subroutine
	LEA      OriginalTodoVector(PC),A0
	MOVE.L   InterruptVectorTodo,(A0)
	LEA      CinepakVintSub(PC),A0
	MOVE     SR,-(A7)
	ORI      #DisableInterruptsTodo,SR
	MOVE.L   A0,InterruptVectorTodo
	MOVE     (A7)+,SR
	RTS

RestoreTodoVector:
	;; depending on the revision may be an unused subroutine
	MOVEA.L  OriginalTodoVector(PC),A0
	MOVE     SR,-(A7)
	ORI      #DisableInterruptsTodo,SR
	MOVE.L   A0,InterruptVectorTodo
	MOVE     (A7)+,SR
	RTS

	;; unused subroutine
	LEA      OriginalTodoVector(PC),A0
	MOVE.L   $5F90,(A0)
	LEA      CinepakVintSub(PC),A0
	MOVE     SR,-(A7)
	ORI      #$0700,SR
	MOVE.L   A0,$5F90
	MOVE     (A7)+,SR
	RTS

	;; unused subroutine
	MOVEA.L  OriginalTodoVector(PC),A0
	MOVE     SR,-(A7)
	ORI      #$0700,SR
	MOVE.L   A0,$5F90
	MOVE     (A7)+,SR
	RTS

HeaderBuffer:	
	dc.l	$00000000

CinepakVintSub:	
	MOVEM.L  D0-D3/A0-A2/A5,-(A7)
CinepakVintSubLoop:	
	MOVEA.L  #$FF8000,A5
	;; check if prepared data present
	MOVE.W   #CDCSTAT,D0
	JSR      _CDBIOS
	BCS      CinepakVintSubEnd
	;; check if no more sectors to read
	MOVE.L   CDDecoderNumberOfSectors(PC),D3
	BEQ      CinepakVintSubEnd
	;; branch if number of sectors is greater than zero
	BGT.S    CinepakVintCDCDMA
	;; negated version of number of sectors
	;; set CDC destination for sub CPU to read
	ANDI.W   #$F8FF,$4(A5)
	ORI.W    #$0300,$4(A5)
	;; set CDC read
	MOVE.W   #CDCREAD,D0
	JSR      _CDBIOS
	;; branch if data not present (which CDCSTAT already indicated it was)
	BCS      CinepakVintSubEnd
	;; check cached destination to store CDC data
	MOVE.L   CDDecoderDestination(PC),D0
	BNE.S    CinepakVintSubHaveDestination
	;; check if can determine place to store CDC data
	BSR      PeekSectDst
	TST.L    D0
	;; if still can't determine place to store CDC data then stop reading
	BEQ      CinepakVintSubStopReading
CinepakVintSubHaveDestination:	
	;; transfer one frame of data to destination location
	MOVEA.L  D0,A0
	LEA      HeaderBuffer(PC),A1
	MOVE.W   #CDCTRN,D0
	JSR      _CDBIOS
	;; if transfer not complete then exit
	BCS      CinepakVintSubEnd
	;; acknowledge the frame was read
 	MOVE.W   #CDCACK,D0
	JSR      _CDBIOS
	;; get next header buffer and masks off to get BCD MM:SS:FF:00
	MOVE.L   HeaderBuffer(PC),D0
	ANDI.L   #$FFFFFF00,D0
	;; covert BCD to binary number
	BSR      ConvertBCDSectorLocationToBinarySectorLocation
	MOVE.L   CDDecoderSectorStart(PC),D1
	CMP.L    D0,D1
	;; branch to end if sector start not equal sector that was read
	BNE.S    CinepakVintSubEnd
	LEA      CDDecoderDestination(PC),A1
	TST.L    (A1)
	BEQ.S    Label07A8
	;; increment destination 
	ADDI.L   #$800,(A1)
	BRA.S    Label07AC
Label07A8:	
	BSR      GetSectDst
Label07AC:	
	NEG.L    D3
	SUBQ.W   #1,D3
	LEA      CDDecoderNumberOfSectors(PC),A1
	MOVE.L   D3,(A1)
	BRA      CinepakVintSubLoop

CinepakVintCDCDMA:	
	;; set CDC destination to program RAM (DMA)
	MOVEA.L  #$FF8000,A5
	ANDI.W   #$F8FF,$4(A5)
	ORI.W    #$0500,$4(A5)
	;; get CDC DMA destination
	MOVE.L   CDDecoderDestination(PC),D0
	BNE.S    CinepakVintCDCDMAHasDestination
	;; don't have a destination yet, get one
	BSR      GetSectDst
	;; check for zero return code, if got one stop reading
	TST.L    D0
	BEQ.S    CinepakVintSubStopReading
CinepakVintCDCDMAHasDestination:	
	;; CDC DMA address shifts address by 3
	LSR.L    #3,D0
	;; set CDC DMA address
	MOVE.W   D0,$A(A5)
	MOVE.W   #CDCREAD,D0
	JSR      _CDBIOS
	BCS.S    CinepakVintSecondCDCReadDataNotPresent
CinepakVintCDCDMAWait:	
	;; wait for EDT (enf of data transfer) to be set
	BTST     #$7,$4(A5)
	BEQ.S    CinepakVintCDCDMAWait
	BCS.S    CinepakVintSecondCDCReadDataNotPresent
	MOVE.W   #CDCACK,D0
	JSR      _CDBIOS
	LEA      CDDecoderDestination(PC),A1
	TST.L    (A1)
	BEQ.S    CinepakVintCDCDMASkipAdd
	;; since we copied increment next destination
	ADDI.L   #$800,(A1)
CinepakVintCDCDMASkipAdd:	
	LEA      CDDecoderNumberOfSectors(PC),A1
	SUBQ.L   #1,(A1)
	BRA      CinepakVintSubLoop
CinepakVintSubEnd:	
	MOVEM.L  (A7)+,D0-D3/A0-A2/A5
	RTS

CinepakVintSubStopReading:	
	;; clear number of sectors (done reading)
	LEA      CDDecoderNumberOfSectors(PC),A1
	CLR.L    (A1)
	BRA.S    CinepakVintSubEnd

CinepakVintSecondCDCReadDataNotPresent:	
	LEA      CDCReadDataNotPresent(PC),A1
	MOVE.L   #$FFFFFFFF,(A1)
	BRA.S    CinepakVintSubEnd

FilmCloseStopCDReading:	
	;; set number of sectors to zero
	LEA      CDDecoderNumberOfSectors(PC),A1
	CLR.L    (A1)
	;; immediately stop any CD reading
	MOVE.W   #ROMPAUSEON,D0
	JSR      _CDBIOS
	RTS

ReadSectors:	
	;; FX args$00.l sector number to start reading from
	;; FX args$04.l number of sectors to read
	;; FX args$08.l place to store data
	;; FX returns d0.l zero successful, non-zero unsuccessful
	;; note the magic five sectors is how many sectors can be buffered
	LINK     A6,#-$04
	MOVEM.L  D1-D7/A0-A5,-(A7)
	;; TODO A5 is unused
	MOVEA.L  #$FF8000,A5
	;; clear CDCReadDataNotPresent
	LEA      CDCReadDataNotPresent(PC),A1
	CLR.L    (A1)
	;; TODO what is d4 for, seems unused
	MOVEQ    #$5,D4
	;; d5 is return code (set to unsuccessful for now)
	MOVEQ    #-$1,D5
	;; TODO what is d4 for, seems unused
	SUBQ.L   #1,D4
	;; TODO d4 should always be 4 so should always not branch
	BEQ      ReadSectorsEnd
	;; make sure number of sectors is greater than zero (signed)
	MOVE.L   Arg04Link(A6),D2
	BLT      ReadSectorsEnd
	;; add 5 to the number of sectors
	MOVEQ    #$5,D2
	ADD.L    D2,Arg04Link(A6)
	;; branch if sector start >= 5 (this doesn't make much sense)
	CMP.L    Arg00Link(A6),D2
	BLE.S    ReadSectorsStartSectorGreaterThan5
	;; will leave number of sectors with 5 added to it
	MOVEQ    #$0,D2
	;; set sector start to sector 0
	MOVE.L   D2,Arg00Link(A6)
ReadSectorsStartSectorGreaterThan5:	
	;; subtract 0 or 5 from the number of sectors
	SUB.L    D2,Arg00Link(A6)
	MOVE.W   #ROMPAUSEON,D0
	JSR      _CDBIOS

ReadSectorsWaitForCommandsToComplete0:	
	MOVE.W   #CDBCHK,D0
	JSR      _CDBIOS
	BCS.S   ReadSectorsWaitForCommandsToComplete0

	MOVE.W   #CDBSTAT,D0
	JSR      _CDBIOS
	;; check bios_status
	MOVE.W   (A0),D0
	;; branch if bios status is stopped (meaning we can use the CD)
	ANDI.W   #$FF00,D0
	BEQ.S    ReadSectorsBiosStatusIndicatesReady
	;; branch if bios status is not paused
	CMPI.W   #$0500,D0
	BNE.S    ReadSectorsWaitForCommandsToComplete0
ReadSectorsBiosStatusIndicatesReady:	
	;; through out current sector
	MOVE.W   #CDCSTOP,D0
	JSR      _CDBIOS
	MOVEQ    #$0,D0
	;; unsure on what this does, assuming makes sure we are in CDC mode
	MOVE.W   #CDCSETMODE,D0
	JSR      _CDBIOS
	MOVE.L   Arg00Link(A6),D3
	MOVE.L   Arg04Link(A6),D2
	;; pass sector start and number of sectors
	LEA      Arg00Link(A6),A0
	MOVE.W   #ROMREADN,D0
	JSR      _CDBIOS
ReadSectorsWaitForCommandsToComplete1:	
	MOVE.W   #CDBCHK,D0
	JSR      _CDBIOS
	BCS.S    ReadSectorsWaitForCommandsToComplete1

	;; save CDDecoderDestination
	LEA      CDDecoderDestination(PC),A1
	MOVE.L   Arg08Link(A6),(A1)
	;; subtract 5 and negate number of sectors and
	;; save CDDecoderNumberOfSectors
	LEA      CDDecoderNumberOfSectors(PC),A1
	SUBQ.L   #5,D2
	NEG.L    D2
	MOVE.L   D2,(A1)
	;; add 5 to sector start and save CDDecoderSectorStart
	LEA      CDDecoderSectorStart(PC),A1
	ADDQ.L   #5,D3
	MOVE.L   D3,(A1)
	;; return zero (successful)
	MOVEQ    #$0,D5
ReadSectorsEnd:	
	MOVE.L   D5,D0
	MOVEM.L  (A7)+,D1-D7/A0-A5
	UNLK     A6
	RTS
	
ConvertBCDByteToBinaryByte:
	;; FX args:d0.b BCD byte
	;; FX destroys:d0[8-15] is cleared
	;; FX returns:d0.b binary byte
	MOVE.L   D1,-(A7)
	MOVE.B   D0,D1
	ANDI.W   #$FF,D1
	LSR.W    #4,D1
	MULU.W   #10,D1
	ANDI.W   #$0F,D0
	ADD.W    D1,D0
	MOVE.L   (A7)+,D1
	RTS

ConvertBCDSectorLocationToBinarySectorLocation:	
	;; FX args:d0.l BCD minutes/seconds/frames/flags in format MM:SS:FF:XX
	;; FX returns:d0.l value as sector location
	MOVEM.L  D1-D2,-(A7)
	;; shift off flags
	LSR.L    #8,D0
	;; save d0 into d1
	MOVE.L   D0,D1
	JSR      ConvertBCDByteToBinaryByte(PC)
	;; save frame count into total
	MOVE.B   D0,D2
	ANDI.L   #$FF,D2
	;; shift off frames
	MOVE.L   D1,D0
	LSR.L    #8,D0
	MOVE.L   D0,D1
	JSR      ConvertBCDByteToBinaryByte(PC)
	ANDI.L   #$FF,D0
	;; multiply second count by 75 (75 frames per second)
	MULU.W   #75,D0
	;; add second count into total
	ADD.L    D0,D2
	;; shift off seconds
	MOVE.L   D1,D0
	LSR.L    #8,D0
	JSR      ConvertBCDByteToBinaryByte(PC)
	ANDI.L   #$FF,D0
	;; multily minute count by 60*75 (60*75 frames per minute)
	MULU.W   #(60*75),D0
	;; add minute count into total
	ADD.L    D0,D2
	;; subtract 2 second pregap
	SUBI.L   #(2*75),D2
	MOVE.L   D2,D0
	MOVEM.L  (A7)+,D1-D2
	RTS

Wait:	
	;; FX args$00.l wait for a certain number of ticks
	LINK     A6,#$0
	MOVEM.L  D6-D7,-(A7)
	MOVE.L   Arg00Link(A6),D6
	;; get start ticks
	JSR      GetTicks(PC)
	MOVE.L   D0,D7
WaitLoop:	
	JSR      GetTicks(PC)
	;; determine target ticks (start ticks + wait number of ticks)
	MOVE.L   D7,D1
	ADD.L    D6,D1
	;; loop if current ticks is less than target ticks
	CMP.L    D0,D1
	BGT.S    WaitLoop
	MOVEM.L  -$8(A6),D6-D7
	UNLK     A6
	RTS

	dc.b	$84,"Wait",$00,$00,$00
	even

FilmOpenLocalVar04:	equ	-$04
FilmOpenMallocAddress:	equ	-$08
FilmOpenLocalVar0C:	equ	-$0C
FilmOpenLocalVar10:	equ	-$10

FilmOpen:	
	;; FX args:$00.l sector start of cinepak file
	;; FX returns:d0.l zero (successful) non-zero (unsuccessful)
	LINK     A6,#-$10
        MOVEM.L  D3-D7/A3-A4,-(A7)
	;; word RAM 1M bit address
	MOVE.L   #WordRAMStart,FilmOpenLocalVar10(A6)
	LEA      SubPlayStart(PC),A0
	;; FilmFXDataOffset
	LEA      $40(A0),A4
	JSR      InitWorld(PC)
	MOVEQ    #-5,D0
	MOVE.L   D0,FilmOpenLocalVar04(A6)
FilmOpenTryAgain:	
	MOVE.L   FilmOpenLocalVar04(A6),D0
	ADDQ.L   #1,FilmOpenLocalVar04(A6)
	TST.L    FilmOpenLocalVar04(A6)
	BNE.S    FilmOpenHasMoreAttempts
	;; ran out of open attempts, report failure
	MOVEQ    #-$1,D0
	BRA      FilmOpenEnd
FilmOpenHasMoreAttempts:	
	;; set STABIndex to $FFFFFFFF
	MOVEQ    #-$1,D0
	MOVE.L   D0,$40(A4)
	;; push one sector size, mmalloc, save malloc address, pop
	PEA      $800
	JSR      mmalloc(PC)
	MOVE.L   D0,FilmOpenMallocAddress(A6)
	ADDQ.W   #4,A7
	;; check if aquired memory
	BNE.S    FilmOpenMalloc0Successful
	MOVEQ    #-$1,D0
	BRA      FilmOpenEnd
FilmOpenMalloc0Successful:	
	;; push malloc address, sector length of 1, and sector start
	;; then call ReadSectors and WaitIODone
	MOVE.L   FilmOpenMallocAddress(A6),-(A7)
	MOVEQ    #1,D0
	MOVE.L   D0,-(A7)
	MOVE.L   Arg00Link(A6),-(A7)
	JSR      ReadSectors(PC)
	JSR      WaitIODone(PC)
	;; test CDCReadDataNotPresent
	LEA      SubPlayStart(PC),A0
	TST.L    $38(A0)
	;; pop args from previous sub calls
	LEA      $C(A7),A7
	;; branch if CDCReadDataNotPresent >= 0
	BGE.S    FilmOpenCDCReadSuccessful
	;; push malloc address, call ffree, pop
	MOVE.L   FilmOpenMallocAddress(A6),-(A7)
	JSR      ffree(PC)
	ADDQ.W   #4,A7
	BRA.S    FilmOpenTryAgain
FilmOpenCDCReadSuccessful:	
	MOVEA.L  FilmOpenMallocAddress(A6),A0
	;; check for FILM signature
	CMPI.L   #'FILM',(A0)
	BEQ.S    FilmOpenVerifiedFILMHeader
	;; did not have FILM header, bad file, 
	MOVEQ    #-$1,D0
	BRA      FilmOpenEnd
FilmOpenVerifiedFILMHeader:	
	MOVEA.L  FilmOpenMallocAddress(A6),A0
	;; get length of FILM header (which includes FDSC and STAB portions)
	MOVE.L   $4(A0),D0
	;; saved, but never used again...
	MOVE.L   D0,FilmOpenLocalVar0C(A6)
	;; determine number of sectors needed for FILM header
	ADD.L    #$7FF,D0
	MOVE.L   #$800,D1
	JSR      SignedDivideLongWordByLongWord(PC)
	MOVE.L   D0,D4
	;; if FILM sector length is only 1 then no need to reread this one
	;; sector
	MOVEQ    #1,D0
	CMP.L    D4,D0
	BGE.S    FilmOpenCDCRead1Successful
	;; free previous read FILM sector since need a bigger size
	MOVE.L   FilmOpenMallocAddress(A6),-(A7)
	JSR      ffree(PC)
	MOVE.L   D4,D1
	;; multiply number of sectors for sector size ($800)
	;; convoluted since trying to do a 16 x 32 out of a 16 x 16 instruction
	;; but since the number of sectors of the FILM header must be less
	;; than $10000 since not enough RAM to read such a large FILM header
	MOVE.L   D1,D0
	MULU.W   #$800,D1
	SWAP     D0
	MULU.W   #$800,D0
	SWAP     D0
	CLR.W    D0
	ADD.L    D0,D1
	;; push memory length required, call mmalloc,
	;; then	pop args for ffree and mmalloc
	MOVE.L   D1,-(A7)
	JSR      mmalloc(PC)
	MOVE.L   D0,FilmOpenMallocAddress(A6)
	ADDQ.W   #8,A7
	;; check if aquired memory
	BNE.S    FilmOpenMalloc1Successful
	;; failed to allocate enough memory for FILM header
	MOVEQ    #-$1,D0
	BRA      FilmOpenEnd
FilmOpenMalloc1Successful:	
	;; push malloc address, sector length of 1, and sector start
	;; then call ReadSectors and WaitIODone
	MOVE.L   FilmOpenMallocAddress(A6),-(A7)
	MOVE.L   D4,-(A7)
	MOVE.L   Arg00Link(A6),-(A7)
	JSR      ReadSectors(PC)
	JSR      WaitIODone(PC)
	;; test CDCReadDataNotPresent
	LEA      SubPlayStart(PC),A0
	TST.L    $38(A0)
	;; pop args from previous sub calls
	LEA      $C(A7),A7
	;; branch if CDCReadDataNotPresent >= 0
	BGE.S    FilmOpenCDCRead1Successful
	;; push malloc address, call ffree, pop
	MOVE.L   FilmOpenMallocAddress(A6),-(A7)
	JSR      ffree(PC)
	ADDQ.W   #4,A7
	BRA      FilmOpenTryAgain
FilmOpenCDCRead1Successful:	
	;; don't need to verify FILM signature again
	MOVEA.L  FilmOpenMallocAddress(A6),A0
	;; save FILM header length to FILMHeaderLength
	MOVE.L   $4(A0),$8(A4)
	;; determine and save FDSCStartAddress
	MOVE.L   FilmOpenMallocAddress(A6),D0
	MOVEQ    #$10,D1
	ADD.L    D1,D0
	MOVE.L   D0,$C(A4)
	;; determine and save STABStartAddress
	MOVEA.L  D0,A0
	MOVE.L   $4(A0),D0
	ADD.L    $C(A4),D0
	MOVE.L   D0,$10(A4)
	;; save malloc address to FILMHeaderHeapAddress
	MOVE.L   FilmOpenMallocAddress(A6),$14(A4)
	;; push sector start, EngineData, call InitEngine, test return code,
	;; and pop args
	MOVE.L   Arg00Link(A6),-(A7)
	PEA      $18(A4)
	JSR      InitEngine(PC)
	TST.W    D0
	ADDQ.W   #8,A7
	BEQ.S    FilmOpenInitEngineSuccessful
	;; InitEngine failed
	MOVEQ    #-$1,D0
	BRA      FilmOpenEnd
FilmOpenInitEngineSuccessful:	
	;; push EngineData, call EngineIdle, clear some regs, pop args
	PEA      $18(A4)
	JSR      EngineIdle(PC)
	MOVEQ    #$0,D5
	MOVE.L   D5,D6
	MOVE.L   D6,D7
	ADDQ.W   #4,A7

	;; determine if have audio STAB entries and frame STAB entries
	BRA.S    FilmOpenCheckIfMoreSTABEntries
FilmOpenCountSTABEntryTypes:	
	;; STABStartAddress
	MOVEA.L  $10(A4),A0
	;; TODO update comment
	;; get STAB entry info, if all ones it is an audio entry, otherwise
	;; it is a frame entry
	MOVE.L   D7,D0
	;; multiply STAB entry index by 16 (length of STAB entry)
	ASL.L    #4,D0
	LEA      $10(A0,D0.L),A3
	MOVEQ    #-$1,D0
	CMP.L    $8(A3),D0
	SEQ      D3
	NEG.B    D3
	BEQ.S    FilmOpenIncrementVideoSTABEntries
	;; increment audio STAB entries
	MOVE.L   D5,D0
	ADDQ.L   #1,D5
	BRA.S    FilmOpenIncrementToNextSTABEntry
FilmOpenIncrementVideoSTABEntries:	
	MOVE.L   D6,D0
	ADDQ.L   #1,D6
FilmOpenIncrementToNextSTABEntry:	
	MOVE.L   D7,D0
	ADDQ.L   #1,D7
FilmOpenCheckIfMoreSTABEntries:	
	;; STABStartAddress
	MOVEA.L  $10(A4),A0
	;; see if current STAB entry is within number of STAB entries
	CMP.L    $C(A0),D7
	BLT.S    FilmOpenCountSTABEntryTypes
	;; clear STABAudioAndVideoInfo (audio and video)
	LEA      SubPlayStart(PC),A0
	MOVEQ    #$0,D0
	MOVE.L   D0,$3C(A0)
	TST.L    D6
	BNE.S    FilmOpenSTABVideoEntryCheckDone
	;; set STABAudioAndVideoInfo to -1 (audio and no video)
	LEA      SubPlayStart(PC),A0
	MOVEQ    #-$1,D0
	MOVE.L   D0,$3C(A0)
FilmOpenSTABVideoEntryCheckDone:	
	TST.L    D5
	BNE.S    FilmOpenSTABFrameEntryCheckDone
	;; set STABAudioAndVideoInfo to 1 (no audio)
	LEA      SubPlayStart(PC),A0
	MOVEQ    #$1,D0
	MOVE.L   D0,$3C(A0)
FilmOpenSTABFrameEntryCheckDone:	
	LEA      SubPlayStart(PC),A0
	;; word RAM 1M bit address
	MOVEA.L  FilmOpenLocalVar10(A6),A1
	;; VRAMTileDataStart
	MOVE.L   $20(A0),(A1)
	LEA      SubPlayStart(PC),A0
	LEA      SubPlayStart(PC),A1
	;; FilmX times 2 (size of tile entry for the VDP)
	MOVE.L   $28(A1),D0
	ADD.L    D0,D0
	;; add VRAMTileMapStart
	ADD.L    $24(A0),D0
	LEA      SubPlayStart(PC),A0
	;; add FilmY * 64*2 (the size of a VDP row)
	MOVE.L   $2C(A0),D1
	ASL.L    #7,D1
	ADD.L    D0,D1
	;; word RAM 1M bit address
	MOVEA.L  FilmOpenLocalVar10(A6),A0
	MOVE.L   D1,$4(A0)
	LEA      SubPlayStart(PC),A0
	;; word RAM 1M bit address
	MOVEA.L  FilmOpenLocalVar10(A6),A1
	;; FilmTilePriority
	MOVE.L   $30(A0),$8(A1)
	JSR      SwapMeg(PC)
	;; send $3 command to main
	MOVEQ    #$3,D0
	MOVE.L   D0,-(A7)
	JSR      SendCommandToMain(PC)
	;; set STABIndex to zero
	MOVEQ    #$0,D0
	MOVE.L   D0,$40(A4)
	;; wait for 60 ticks
	MOVEQ    #60,D0
	MOVE.L   D0,-(A7)
	JSR      Wait(PC)
	;; push AudioData, call AudioInit
	PEA      $30(A4)
	JSR      AudioInit(PC)
	;; set successful return code
	MOVEQ    #$0,D0
	;; pop arguments to SendCommandToMain, Wait, and AudioInit
	LEA      $C(A7),A7
FilmOpenEnd:	
	MOVEM.L  -$2C(A6),D3-D7/A3-A4
	UNLK     A6
	RTS

	dc.b	$88,"FilmOpen",$00,$00,$00
	even

FilmTask:	
	LINK     A6,#-$04
	MOVEM.L  D6-D7/A3-A4,-(A7)
	;; save word RAM 1M bit address in local var
	MOVE.L   #WordRAMStart,LocalVar04(A6)
	LEA      SubPlayStart(PC),A0
	;; FilmFXDataOffset
	LEA      $40(A0),A4
	LEA      SubPlayStart(PC),A0
	;; CDCReadDataNotPresent
	TST.L    $38(A0)
	BEQ.S    FilmTaskDataPresent
	MOVEQ    #-$2,D0
	BRA      FilmTaskEnd
FilmTaskDataPresent:	
	;; test if STABIndex is greater or equal to zero
	TST.L    $40(A4)
	BGE.S    FilmTaskMoreSTABEntries
	MOVEQ    #-$1,D0
	BRA      FilmTaskEnd
FilmTaskMoreSTABEntries:
	;; use AudioSilence if no audio specified in STABAudioAndVideoInfo
	LEA      SubPlayStart(PC),A0
	MOVEQ    #$1,D0
	CMP.L    $3C(A0),D0
	BNE.S    FilmTaskSilenceHandled
	;; push AudioData, call AudioSilence, pop args
	PEA      $30(A4)
	JSR      AudioSilence(PC)
	ADDQ.W   #4,A7
FilmTaskSilenceHandled:	
	;; push AudioData, call AudioIdle
	PEA      $30(A4)
	JSR      AudioIdle(PC)
	;; push EngineData, call EngineIdle, clear some regs
	PEA      $18(A4)
	JSR      EngineIdle(PC)
	;; determine STABEntry location (STABStartAddress+16*STABIndex+16)
	MOVEA.L  $10(A4),A0
	MOVE.L   $40(A4),D0
	;; increment STABIndex
	ADDQ.L   #1,$40(A4)
	ASL.L    #4,D0
	LEA      $10(A0,D0.L),A3
	MOVEQ    #-$1,D0
	CMP.L    $8(A3),D0
	;; pop args from AudioIdle and EngineIdle
	ADDQ.W   #8,A7
	BNE      FilmTaskEntryIsVideo
	BRA.S    FilmTaskEntryIsAudio

FilmTaskAudioLoop:	
	;; push EngineData, call EngineIdle
	PEA      $18(A4)
	JSR      EngineIdle(PC)
	;; push AudioData, call AudioIdle
	PEA      $30(A4)
	JSR      AudioIdle(PC)
	;; pop args from EngineIdle and AudioIdle
	ADDQ.W   #8,A7
FilmTaskEntryIsAudio:	
	;; push data length
	MOVE.L   $4(A3),-(A7)
	;; push data start (STAB entry start + FILMHeaderLength)
	MOVE.L   (A3),D0
	ADD.L    $8(A4),D0
	MOVE.L   D0,-(A7)
	;; push EngineData
	PEA      $18(A4)
	;; call GetData, save d0 to d7, pop args
	JSR      GetData(PC)
	MOVE.L   D0,D7
	LEA      $C(A7),A7
	;; loop while GetData returns zero
	BEQ.S    FilmTaskAudioLoop
	;; check if GetData reported error
	MOVEQ    #-$1,D0
	CMP.L    D7,D0
	BNE.S    FilmTaskGetDataRanClean
	;; GetData had failure, so quit
	MOVEQ    #-$2,D0
	BRA      FilmTaskEnd
FilmTaskGetDataRanClean:	
	;; push data length, data start, AudioData, call Audio Feed
	MOVE.L   $4(A3),-(A7)
	MOVE.L   D7,-(A7)
	PEA      $30(A4)
	JSR      AudioFeed(PC)
	;; if STABAudioAndVideoInfo indicates has audio, check if audio busy
	LEA      SubPlayStart(PC),A0
	MOVEQ    #-$1,D0
	CMP.L    $3C(A0),D0
	LEA      $C(A7),A7
	BNE.S    FilmTaskAudioNotBusy
	BRA.S    FilmTaskCheckAudioBusyCond

FilmTaskCheckAudioBusyLoop:	
	;; push AudioData, call AudioIdle, pop args
	PEA      $30(A4)
	JSR      AudioIdle(PC)
	ADDQ.W   #4,A7
FilmTaskCheckAudioBusyCond:	
	;; push AudioData, call AudioBusy, test return code, pop args
	PEA      $30(A4)
	JSR      AudioBusy(PC)
	TST.W    D0
	ADDQ.W   #4,A7
	BNE.S    FilmTaskCheckAudioBusyLoop
FilmTaskAudioNotBusy:	
	;; push data length
	MOVE.L   $4(A3),-(A7)
	;; push data start (STAB entry start + FILMHeaderLength)
	MOVE.L   (A3),D0
	ADD.L    $8(A4),D0
	MOVE.L   D0,-(A7)
	;; push EngineData
	PEA      $18(A4)
	;; call ReadData, pop args
	JSR      ReadData(PC)
	LEA      $C(A7),A7
	BRA      FilmTaskCheckMoreSTABEntries

FilmTaskVideoLoop:
	;; push AudioData, call AudioIdle
	PEA      $30(A4)
	JSR      AudioIdle(PC)
	;; push EngineData, call EngineIdle
	PEA      $18(A4)
	JSR      EngineIdle(PC)
	;; pop args from AudioIdle and EngineIdle
	ADDQ.W   #8,A7
FilmTaskEntryIsVideo:	
	;; push data length
	MOVE.L   $4(A3),-(A7)
	;; push data start (STAB entry start + FILMHeaderLength)
	MOVE.L   (A3),D0
	ADD.L    $8(A4),D0
	MOVE.L   D0,-(A7)
	;; push EngineData
	PEA      $18(A4)
	;; call GetData, save d0 into d7, pop args
	JSR      GetData(PC)
	MOVE.L   D0,D7
	LEA      $C(A7),A7
	BEQ.S    FilmTaskVideoLoop
	;; check GetData for error
	MOVEQ    #-$1,D0
	CMP.L    D7,D0
	BNE.S    FilmTaskVideoFrameDataIsReady
	;; GetData had failure, so quit
	MOVEQ    #-$2,D0
	BRA      FilmTaskEnd
FilmTaskVideoFrameDataIsReady:
	;; start of video frame data
	MOVEA.L  D7,A0
	;; check for SM
	CMPI.W   #'SM',(A0)
	BNE.S    FilmTaskVideoFrameBadHeader
	MOVE.L   #$7FFFFFFF,D6
	;; STAB entry value1 & $7FFFFFFF
	AND.L    $8(A3),D6
	;; push start of video frame data
	MOVE.L   D7,-(A7)
	;; push start of Word RAM 1 Meg
	MOVE.L   LocalVar04(A6),-(A7)
	;; call DecodeFrame
	JSR      DecodeFrame(PC)
	BRA.S    FilmTaskVideoFrameWaitCondition

FilmTaskVideoFrameWaitLoop:	
	;; push AudioData, call AudioIdle
	PEA      $30(A4)
	JSR      AudioIdle(PC)
	;; push EngineData, call EngineIdle
	PEA      $18(A4)
	JSR      EngineIdle(PC)
	;; pop args from AudioIdle and EngineIdle
	ADDQ.W   #8,A7
FilmTaskVideoFrameWaitCondition:	
	;; push 600 and AudioData, call AudioTime
	;; does not use the 600 that is in most STAB headers
	PEA      FixedBaseRate
	PEA      $30(A4)
	JSR      AudioTime(PC)
	CMP.L    D0,D6
	;; pop args from AudioTime
	ADDQ.W   #8,A7
	BGT.S    FilmTaskVideoFrameWaitLoop
	
	;; ok, swap the Megs in the Word RAM
	JSR      SwapMeg(PC)
	;; send $2 command to main to indicate next video frame
	MOVEQ    #$2,D0
	MOVE.L   D0,-(A7)
	JSR      SendCommandToMain(PC)
	ADDQ.W   #4,A7

FilmTaskVideoFrameBadHeader:	
	;; push data length
	MOVE.L   $4(A3),-(A7)
	;; push data start (STAB entry start + FILMHeaderLength)
	MOVE.L   (A3),D0
	ADD.L    $8(A4),D0
	MOVE.L   D0,-(A7)
	;; push EngineData
	PEA      $18(A4)
	;; call ReadData, pop args
	JSR      ReadData(PC)
	LEA      $C(A7),A7

FilmTaskCheckMoreSTABEntries:	
	;; check STABIndex against STAB entry count (STABStartAddress+$C)
	MOVEA.L  $10(A4),A0
	MOVE.L   $40(A4),D0
	CMP.L    $C(A0),D0
	BNE.S    FilmTaskHasMoreSTABEntries
	;; set STABIndex to $FFFFFFFF (no more STAB entries)
	MOVEQ    #-$1,D0
	MOVE.L   D0,$40(A4)
FilmTaskHasMoreSTABEntries:	
	;; return STABIndex as return code
	MOVE.L   $40(A4),D0
FilmTaskEnd:	
	MOVEM.L  -$14(A6),D6-D7/A3-A4
	UNLK     A6
	RTS

	dc.b	$88,"FilmTask",$00,$00,$00
	even
	
FilmClose:	
	LINK     A6,#$0
	MOVE.L   A4,-(A7)
	LEA      SubPlayStart(PC),A0
	;; FilmFXDataOffset
	LEA      $40(A0),A4
	;; push AudioData, call AudioStop
	PEA      $30(A4)
	JSR      AudioStop(PC)
	;; call FilmCloseStopCDReading
	JSR      FilmCloseStopCDReading(PC)
	;; push EngineDataBufferStart, call DisposeEngine
	PEA      $18(A4)
	JSR      DisposeEngine(PC)
	;; set STABIndex to $FFFFFFFF
	MOVEQ    #-$1,D0
	MOVE.L   D0,$40(A4)
	;; free FILMHeaderHeapAddress
	MOVE.L   $14(A4),-(A7)
	JSR      ffree(PC)
	;; send $3 command to main
	MOVEQ    #$3,D0
	MOVE.L   D0,-(A7)
	JSR      SendCommandToMain(PC)
	MOVEQ    #$0,D0
	MOVEA.L  -$4(A6),A4
	UNLK     A6
	RTS

	dc.b	$89,"FilmClose",$00,$00
	even

SwapMeg:	
	LINK     A6,#$0
	;; to get here sub is ready to swap meg
	;; need to wait for main to be ready to swap meg
SwapMegWaitForZeroCommand:	
	MOVEA.L  #$FF8010,A0
	TST.W    $2(A0)
	BNE.S    SwapMegWaitForZeroCommand
	;; swap the main/sub control of the two 1Mbit areas in word RAM
	EORI.W   #$1,($FF8002).L
	UNLK     A6
	RTS

	dc.b	$87,"SwapMeg",$00,$00
	even

	
ffree:
	;; FX args$00.l address to free
	LINK     A6,#$0
	MOVEM.L  A3-A4,-(A7)
	MOVEA.L  Arg00Link(A6),A3
	MOVEA.L  A3,A4
	;; get heap entry header from heap entry value address
	LEA      -$10(A4),A4
	;; make sure heap entry value address is not zero
	;; (since malloc returns 0 address on failure)
	MOVE.L   A3,D0
	BEQ.S    ffreeBadHeapEntry
	;; make sure heap entry is in the used state
	MOVEQ    #HeapEntryZeroAll,D0
	CMP.B    (A4),D0
	BNE.S    ffreeBadHeapEntry
	MOVEQ    #HeapEntryOneNotEnd,D0
	CMP.B    HeapEntryOffsetOne(A4),D0
	BNE.S    ffreeBadHeapEntry
	TST.W    HeapEntryOffsetUsage(A4)
	BEQ.S    ffreeBadHeapEntry
	;; valid heap entry, not update previous entry
	TST.L    HeapEntryOffsetPreviousEntry(A4)
	BEQ.S    ffreeDoNotCombineFreedEntryWithPreviousEntry
	MOVEA.L  HeapEntryOffsetPreviousEntry(A4),A0
	;; if previous entry is used, cannot combine heap entries
	TST.W    HeapEntryOffsetUsage(A0)
	BNE.S    ffreeDoNotCombineFreedEntryWithPreviousEntry
	;; set previous entry's next to freed entries next
	MOVE.L   HeapEntryOffsetNextEntry(A4),HeapEntryOffsetNextEntry(A0)
	;; set a4 to previous entry (before freed entry)
	MOVEA.L  HeapEntryOffsetPreviousEntry(A4),A4
	;; set a0 to next entry (after freed entry)
	MOVEA.L  HeapEntryOffsetNextEntry(A4),A0
	;; set next entry's previous entry to previous entry
	MOVE.L   A4,HeapEntryOffsetPreviousEntry(A0)
ffreeDoNotCombineFreedEntryWithPreviousEntry:	
	;; test if next entry is not available
	MOVEA.L  HeapEntryOffsetNextEntry(A4),A0
	TST.W    HeapEntryOffsetUsage(A0)
	BNE.S    ffreeDoNotCombineFreedEntryWithNextEntry
	;; next entry is available, so combine with freed entry
	;; get the next entry's next entry
	MOVEA.L  HeapEntryOffsetNextEntry(A0),A0
	;; set freed entry's next entry to next entry's next entry
	MOVE.L   A0,HeapEntryOffsetNextEntry(A4)
	;; set next entry's next entry's previous entry to freed entry
	MOVE.L   A4,HeapEntryOffsetPreviousEntry(A0)
ffreeDoNotCombineFreedEntryWithNextEntry:	
	;; set (potentially combined) freed entry to HeapEntryUsageAvailable
	CLR.W    HeapEntryOffsetUsage(A4)
ffreeBadHeapEntry:	
	MOVEM.L  -$8(A6),A3-A4
	UNLK     A6
	RTS

	dc.b	$85,"ffree",$00,$00
	even

findSmallestBlock:	
	;; FX args:$00.l block size to search best fit for
findSmallestBlockArgBlockSize:	equ	$8
	;; FX returns heap entry address (or zero if none found)
	LINK     A6,#$0
	MOVEM.L  D5-D7/A3-A4,-(A7)
	MOVE.L   findSmallestBlockArgBlockSize(A6),D5
	;; HeapStart
	LEA      SubPlayStart(PC),A0
	MOVEA.L  $8(A0),A4
	;; will return 0 if no suitable block
	MOVEQ    #$0,D0
	MOVEA.L  D0,A3
	;; largest signed number, so any available block should be smaller
	MOVE.L   #$7FFFFFFF,D6
	BRA.S    findSmallestBlockCheckLastHeapEntry
findSmallestBlockLoop:	
	TST.W    HeapEntryOffsetUsage(A4)
	BNE.S    findSmallestBlockGetNextHeapEntry
	;; determine size of entry by subtracting next heap entry start
	MOVE.L   HeapEntryOffsetNextEntry(A4),D0
	SUB.L    A4,D0
	MOVE.L   D0,D7
	;; subtract 16 bytes for the heap entry
	MOVEQ    #HeapEntryLength,D0
	SUB.L    D0,D7
	;; see if size large enough requested size and smaller than any
	;; other valid blocks
	CMP.L    D7,D5
	BGE.S    findSmallestBlockGetNextHeapEntry
	CMP.L    D7,D6
	BLE.S    findSmallestBlockGetNextHeapEntry
	;; found smaller block, save size and start location
	MOVE.L   D7,D6
	MOVEA.L  A4,A3
findSmallestBlockGetNextHeapEntry:	
	MOVEA.L  $8(A4),A4
findSmallestBlockCheckLastHeapEntry:	
	MOVE.L   A4,D0
	BNE.S    findSmallestBlockLoop
	;; save winning block to d0
	MOVE.L   A3,D0
	MOVEM.L  -$14(A6),D5-D7/A3-A4
	UNLK     A6
	RTS

	dc.b	$91,"findSmallestBlock",$00,$00
	even

MaxFree:
	;; FX returns d0.l max free chunk in heap (-1 if none available)
	LINK     A6,#$0
	MOVEM.L  D6-D7/A4,-(A7)
	LEA      SubPlayStart(PC),A0
	;; HeapStart
	MOVEA.L  $8(A0),A4
	MOVEQ    #-$1,D6
	BRA.S    MaxFreeLoopCond
MaxFreeLoop:	
	;; only check available chunks
	TST.W    HeapEntryOffsetUsage(A4)
	BNE.S    MaxFreeLoopEntryNotLargeer
	;; determine size between heap entries
	MOVE.L   HeapEntryOffsetNextEntry(A4),D0
	SUB.L    A4,D0
	;; subtract heap entry header length
	MOVE.L   D0,D7
	MOVEQ    #HeapEntryLength,D0
	SUB.L    D0,D7
	;; compare heap entry size against current largest heap entry
	CMP.L    D7,D6
	BGE.S    MaxFreeLoopEntryNotLargeer
	;; save this heap entry size since largest
	MOVE.L   D7,D6
MaxFreeLoopEntryNotLargeer:	
	;; skip to next heap entry
	MOVEA.L  HeapEntryOffsetNextEntry(A4),A4
MaxFreeLoopCond:	
	;; tests if heap entry address is zero (no more entries)
	MOVE.L   A4,D0
	BNE.S    MaxFreeLoop
	;; save max free chunk
	MOVE.L   D6,D0
	MOVEM.L  -$C(A6),D6-D7/A4
	UNLK     A6
	RTS

	dc.b	$87,"MaxFree",$00,$00
	even

mmalloc:	
	;; FX args:$00.l block size to malloc
mmallocArgBlockSize:	equ	$8
	;; FX returns address of malloc memory (or zero on failure)
	LINK     A6,#$0
	MOVEM.L  D6-D7/A3-A4,-(A7)
	MOVE.L   mmallocArgBlockSize(A6),D7
	;; if not on an 8 byte boundary increase size to next 8 byte boundary
	MOVE.L   D7,D0
	ADDQ.L   #7,D0
	MOVEQ    #-$8,D1
	AND.L    D0,D1
	MOVE.L   D1,D7

	;; push block size on stack, call findSmallestBlock,
	;; get returned entry and pop
	MOVE.L   D7,-(A7)
	JSR      findSmallestBlock(PC)
	;; returned heap entry is in d0
	MOVEA.L  D0,A3
	MOVE.L   A3,D0
	ADDQ.W   #4,A7

	;; if heap entry not found, return zero (no memory allocated)
	BNE.S    mmallocFoundBlock
	MOVEQ    #$0,D0
	BRA.S    Endmalloc

mmallocFoundBlock:	
	;; get heap entry size minus heap entry length size
	MOVE.L   HeapEntryOffsetNextEntry(A3),D0
	SUB.L    A3,D0
	MOVE.L   D0,D6
	MOVEQ    #HeapEntryLength,D0
	SUB.L    D0,D6

	;; get requested block size + $18 (so we can allocate
	;; heap entry and an minimal 8 byte heap entry value)
	MOVE.L   D7,D1
	MOVEQ    #$18,D2
	ADD.L    D2,D1

	;; see if enough space for another heap entry in remaining space
	CMP.L    D6,D1
	BGE.S    mmallocSetBlockUsed

	;; determine heap entry for remaining space
	;; (add size and heap entry length)
	MOVE.L   A3,D0
	MOVEQ    #HeapEntryLength,D1
	ADD.L    D1,D0
	MOVEA.L  D0,A4
	ADDA.L   D7,A4

	;; setup new available heap entry
	MOVE.B   #HeapEntryZeroAll,(A4)
	MOVE.B   #HeapEntryOneNotEnd,HeapEntryOffsetOne(A4)
	CLR.W    HeapEntryOffsetUsage(A4)
	MOVE.L   A3,HeapEntryOffsetPreviousEntry(A4)
	MOVE.L   HeapEntryOffsetNextEntry(A3),HeapEntryOffsetNextEntry(A4)
	MOVEQ    #$0,D0
	MOVE.L   D0,HeapEntryOffsetZeros(A4)
	;; setup heap entry after new availble heap entry with new previous
	MOVEA.L  HeapEntryOffsetNextEntry(A4),A0
	MOVE.L   A4,HeapEntryOffsetPreviousEntry(A0)
	;; setup current heap entry to point to new available heap entry
	MOVE.L   A4,$8(A3)
mmallocSetBlockUsed:	
	;; set current heap entry to used
	MOVE.W   #HeapEntryUsageUsed,HeapEntryOffsetUsage(A3)
	;; return address of heap data which is heap entry start plus length
	MOVE.L   A3,D0
	MOVEQ    #HeapEntryLength,D1
	ADD.L    D1,D0
Endmalloc:	
	MOVEM.L  -$10(A6),D6-D7/A3-A4
	UNLK     A6
	RTS

	dc.b	$87,"mmalloc",$00,$00
	even

InitHeap:	
	LINK     A6,#$0
	MOVEM.L  A3-A4,-(A7)
	LEA      SubPlayStart(PC),A0
	;; HeapStart
	MOVEA.L  $8(A0),A3
	LEA      SubPlayStart(PC),A0
	LEA      SubPlayStart(PC),A1
	;; HeapLength
	MOVE.L   $C(A1),D0
	;; HeapStart
	;; determine end of heap
	ADD.L    $8(A0),D0
	MOVEA.L  D0,A4
	;; determine start of last 16 bytes of heap for the last heap entry
	LEA      -$10(A4),A4
	;; save info in first 16 bytes of heap
	MOVE.B   #HeapEntryZeroAll,(A3)
	MOVE.B   #HeapEntryOneNotEnd,HeapEntryOffsetOne(A3)
	;; set to HeapEntryUsageAvailable
	CLR.W    HeapEntryOffsetUsage(A3)
	MOVEQ    #$0,D0
	MOVE.L   D0,HeapEntryOffsetPreviousEntry(A3)
	MOVE.L   A4,HeapEntryOffsetNextEntry(A3)
	MOVE.L   D0,HeapEntryOffsetZeros(A3)
	;; save info in last 16 bytes of heap
	MOVE.B   #HeapEntryZeroAll,(A4)
	MOVE.B   #HeapEntryOneEnd,HeapEntryOffsetOne(A4)
	MOVE.W   #HeapEntryUsageEnd,HeapEntryOffsetUsage(A4)
	MOVE.L   A3,HeapEntryOffsetPreviousEntry(A4)
	MOVE.L   D0,HeapEntryOffsetNextEntry(A4)
	MOVE.L   D0,HeapEntryOffsetZeros(A4)
	MOVEM.L  -$8(A6),A3-A4
	UNLK     A6
	RTS

	dc.b	$88,"InitHeap",$00,$00,$00
	even

InitWorld:	
	LINK     A6,#$0
	MOVE.L   D7,-(A7)
	;; clear communication status
	CLR.W    ($FF8020).L

	;; determine heap start offset from 16 byte boundary
	LEA      SubPlayStart(PC),A0
	MOVEQ    #$F,D7
	;; ensure heap start on 16 byte boundary
	AND.L    $8(A0),D7
	LEA      SubPlayStart(PC),A0
	MOVE.L   $8(A0),D0
	MOVEQ    #$F,D1
	ADD.L    D1,D0
	MOVEQ    #-$10,D2
	AND.L    D0,D2
	LEA      SubPlayStart(PC),A0
	MOVE.L   D2,$8(A0)

	;; if heap start offset from 16 byte boundary, subtract from
	;; heap length
	;; also, ensure heap length is on 16 byte boundary
	LEA      SubPlayStart(PC),A0
	MOVE.L   $C(A0),D0
	SUB.L    D7,D0
	MOVEQ    #-$10,D2
	AND.L    D0,D2
	LEA      SubPlayStart(PC),A0
	MOVE.L   D2,$C(A0)
	LEA      SubPlayStart(PC),A0

	;; if we have any heap available, initialize the heap
	TST.L    $C(A0)
	BEQ.S    EndInitWorld
	JSR      InitHeap(PC)
EndInitWorld:	
	MOVE.L   -$4(A6),D7
	UNLK     A6
	RTS

	dc.b	$89,"InitWorld",$00,$00
	even

GetTicks:	
	LINK     A6,#$0
	LEA      SubPlayStart(PC),A0
	;; get Ticks
	MOVE.L   $4(A0),D0
	UNLK     A6
	RTS

	dc.b	$88,"GetTicks",$00,$00,$00
	even

WaitIODone:	
	LINK     A6,#$0
	MOVE.L   D7,-(A7)
	;; use Ticks to make a 600 tick timeout (10 seconds NTSC)
	LEA      SubPlayStart(PC),A0
	MOVE.L   $4(A0),D7
	ADD.L    #601,D7
WaitIODoneLoop:	
	LEA      SubPlayStart(PC),A0
	MOVEQ    #-$1,D0
	;; CDCReadDataNotPresent
	CMP.L    $38(A0),D0
	BEQ.S    WaitIODoneCheckTimeOut
	LEA      SubPlayStart(PC),A0
	;; CDDecoderNumberOfSectors
	TST.L    $10(A0)
	BEQ.S    WaitIODoneCheckTimeOut
	;; see if timeout on current Ticks
	LEA      SubPlayStart(PC),A0
	CMP.L    $4(A0),D7
	BGT.S    WaitIODoneLoop
WaitIODoneCheckTimeOut:	
	;; see if timeout on current Ticks
	LEA      SubPlayStart(PC),A0
	CMP.L    $4(A0),D7
	BGT.S    WaitIODoneNoTimeOut
	;; timeout occured so make indicate bad read
	LEA      SubPlayStart(PC),A0
	MOVEQ    #-$1,D0
	;; CDCReadDataNotPresent
	MOVE.L   D0,$38(A0)
WaitIODoneNoTimeOut:	
	MOVE.L   -$4(A6),D7
	UNLK     A6
	RTS

	dc.b	$8A,"WaitIODone",$00,$00,$00
	even

	;; stack has long word (though only word used) to send to main
SendCommandToMain:
	LINK     A6,#$0
	MOVEM.L  A3-A4,-(A7)
	;; 16 bytes area for main to send to to sub
	MOVEA.L  #$FF8010,A4
	;; 16 bytes area for sub to send to main
	MOVEA.L  #$FF8020,A3
SendCommandToMainWaitForZero:	
	TST.W    (A4)
	BNE.S    SendCommandToMainWaitForZero
	;; send command specified
	MOVE.W   $A(A6),(A3)
SendCommandToMainWaitForNonZero:
	TST.W    (A4)
	BEQ.S    SendCommandToMainWaitForNonZero
	;; send zero command
	CLR.W    (A3)
	MOVEM.L  -$8(A6),A3-A4
	UNLK     A6
	RTS

	dc.b	$91,"SendCommandToMain",$00,$00
	even

CopyData:
	;; FX args$00.l: source address
	;; FX args$04.l: destination address
	;; FX args$08.l: number of bytes to copy
	;; FX destroys a0.l, a1.l, d0.l
	LINK     A6,#$0
	MOVEM.L  D4-D7,-(A7)
	MOVE.L   Arg04Link(A6),D4
	MOVE.L   Arg00Link(A6),D5
	MOVE.L   Arg08Link(A6),D6
	;; determine number of double words to copy (rounding up)
	MOVE.L   D6,D0
	ADDQ.L   #3,D0
	ASR.L    #2,D0
	MOVE.L   D0,D6
	;; number of double words copied so far (zero)
	MOVEQ    #$0,D7
	BRA.S    CopyDataCondition
CopyDataLoop:	
	;; get source address
	MOVEA.L  D5,A0
	;; increment source address
	ADDQ.L   #4,D5
	;; get destination address
	MOVEA.L  D4,A1
	;; increment destination address
	ADDQ.L   #4,D4
	;; move double word from source to destination
	MOVE.L   (A0),(A1)
	;; TODO useless move
	MOVE.L   D7,D0
	;; increment number of double words copied
	ADDQ.L   #1,D7
CopyDataCondition:
	;; see if we have copied enough double words so far
	CMP.L    D7,D6
	BGT.S    CopyDataLoop
	MOVEM.L  -$10(A6),D4-D7
	UNLK     A6
	RTS

	dc.b	$88,"CopyData",$00,$00,$00
	even

InitEngine:	
	;; FX args$00.l: EngineData
	;; FX args$04.l: sector start
	LINK     A6,#$0
	MOVEM.L  D7/A4,-(A7)
	;; EngineData
	MOVEA.L  Arg00Link(A6),A4
	;; see if we can allocate $14000 chunk (TODO 32 sectors + 16 audio sectors)
	JSR      MaxFree(PC)
	MOVE.L   D0,D7
	CMPI.L   #(EngineDataMinSize+EngineDataWrapAroundBufferSize),D7
	BGE.S    InitEngineCanAllocateMinSize
	;; set failure return code and exit
	MOVEQ    #-$1,D0
	BRA.S    InitEngineDone
InitEngineCanAllocateMinSize:
	CMPI.L   #(EngineDataMaxSize+EngineDataWrapAroundBufferSize),D7
	BLE.S    InitEngineCannotAllocateMaxSize
	;; cap max free to $34000 (TODO 96 sectors + 16 audio sectors)
	MOVE.L   #(EngineDataMaxSize+EngineDataWrapAroundBufferSize),D7
InitEngineCannotAllocateMaxSize:
	MOVE.L   D7,D0
	;; subtract WrapAroundBufferSize
	SUB.L    #EngineDataWrapAroundBufferSize,D0
	;; make sure alloated size is on sector boundary
	MOVE.L   #$800,D1
	JSR      SignedDivideLongWordByLongWord(PC)
	MOVE.L   D0,D1
	MULU.W   #$800,D0
	SWAP     D1
	MULU.W   #$800,D1
	SWAP     D1
	CLR.W    D1
	ADD.L    D1,D0
	;; save allocated amount minus $4000 to EngineDataBufferSize
	MOVE.L   D0,$4(A4)
	;; add WrapAroundBufferSize
	ADD.L    #EngineDataWrapAroundBufferSize,D0
	;; push allocate size, call mmalloc, save mmalloc address, pop args
	MOVE.L   D0,-(A7)
	JSR      mmalloc(PC)
	MOVE.L   D0,(A4)
	ADDQ.W   #4,A7
	BNE.S    InitEngineMallocSuccessful
	;; set failure return code and exit
	MOVEQ    #-$1,D0
	BRA.S    InitEngineDone
InitEngineMallocSuccessful:	
	MOVEQ    #$0,D0
	;; clear Label0060
	MOVE.L   D0,$8(A4)
	;; clear EngineReadDataEnd
	MOVE.L   D0,$C(A4)
	;; set EngineSectorStart to sector start passed in
	MOVE.L   Arg04Link(A6),$10(A4)
	;; clear EngineDataUnused
	MOVE.L   D0,$14(A4)
	LEA      SubPlayStart(PC),A0
	;; save address of EngineData to EngineDataPointer
	MOVE.L   A4,$1C(A0)
	;; push EngineData, call EngineIdle, set success return code, pop args
	MOVE.L   A4,-(A7)
	JSR      EngineIdle(PC)
	MOVEQ    #$0,D0
	ADDQ.W   #4,A7
InitEngineDone:	
	MOVEM.L  -$8(A6),D7/A4
	UNLK     A6
	RTS

	dc.b	$8A,"InitEngine",$00,$00,$00
	even

DisposeEngine:	
	;; FX args$00.l: EngineData
	LINK     A6,#$0
	MOVE.L   A4,-(A7)
	;; EngineDataBufferStart
	MOVEA.L  Arg00Link(A6),A4
	;; if EngineDataBufferStart is zero, nothing to delete
	TST.L    (A4)
	BEQ.S    DisposeEngineDone
	;; push EngineDataAddress, call ffree, pop args
	MOVE.L   (A4),-(A7)
	JSR      ffree(PC)
	ADDQ.W   #4,A7
DisposeEngineDone:	
	MOVEA.L  -$4(A6),A4
	UNLK     A6
	RTS

	dc.b	$8D,"DisposeEngine",$00,$00
	even

GetSectDst:	
	;; FX returns d0.l: sector destination (or zero on error)
	LINK     A6,#$0
	MOVEM.L  D7/A4,-(A7)
	LEA      SubPlayStart(PC),A0
	;; get EngineData from EngineDataPointer
	MOVEA.L  $1C(A0),A4
	;; set default destination of zero
	MOVEQ    #$0,D7
	;; branch if (Label0060-EngineReadDataEnd+$800) >= EngineDataBufferSize
	MOVE.L   $8(A4),D0
	;; EngineReadDataEnd
	SUB.L    $C(A4),D0
	ADD.L    #$800,D0
	CMP.L    $4(A4),D0
	BGE.S    GetSectDstDone
	;; calculate (Label0060%EngineDataBufferSize)
	MOVE.L   $8(A4),D0
	MOVE.L   $4(A4),D1
	JSR      SignedDivideRemainderLongWordByLongWord(PC)
	;; setup return value:
	;; (Label0060%EngineDataBufferSize)+EngineDataBufferStart
	MOVE.L   D0,D7
	ADD.L    (A4),D7
	;; increment EngineSectorStart
	ADDQ.L   #1,$10(A4)
	;; add sector size to Label0060
	ADDI.L   #$800,$8(A4)
GetSectDstDone:	
	MOVE.L   D7,D0
	MOVEM.L  -$8(A6),D7/A4
	UNLK     A6
	RTS

	dc.b	$8A,"GetSectDst",$00,$00,$00
	even

PeekSectDst:	
	;; FX returns d0.l: sector destination (or zero on error)
	LINK     A6,#$0
	MOVEM.L  D7/A4,-(A7)
	LEA      SubPlayStart(PC),A0
	;; get EngineData from EngineDataPointer
	MOVEA.L  $1C(A0),A4
	;; set default destination of zero
	MOVEQ    #$0,D7
	;; branch if (Label0060-EngineReadDataEnd+$800) >= EngineDataBufferSize
	MOVE.L   $8(A4),D0
	SUB.L    $C(A4),D0
	ADD.L    #$800,D0
	CMP.L    $4(A4),D0
	BGE.S    PeekSectDstDone
	;; calculate (Label0060%EngineDataBufferSize)
	MOVE.L   $8(A4),D0
	MOVE.L   $4(A4),D1
	JSR      SignedDivideRemainderLongWordByLongWord(PC)
	;; setup return value:
	;; (Label0060%EngineDataBufferSize)+EngineDataBufferStart
	MOVE.L   D0,D7
	ADD.L    (A4),D7
PeekSectDstDone:	
	MOVE.L   D7,D0
	MOVEM.L  -$8(A6),D7/A4
	UNLK     A6
	RTS

	dc.b	$8B,"PeekSectDst",$00,$00
	even

EngineIdle:	
	;; FX args$00.l: EngineData
	LINK     A6,#$0
	MOVE.L   A4,-(A7)
	MOVEA.L  Arg00Link(A6),A4
	;; CDDecoderNumberOfSectors
	LEA      SubPlayStart(PC),A0
	TST.L    $10(A0)
	BNE.S    EngineIdleDone
	;; TODO reading more data when less than or equal $8000 bytes remaining
	;; branch if:
	;; EngineDataBufferSize-(Label0060-EngineReadDataEnd) <= $8000
	MOVE.L   $8(A4),D0
	SUB.L    $C(A4),D0
	MOVE.L   $4(A4),D1
	SUB.L    D0,D1
	CMPI.L   #$8000,D1
	BLT.S    EngineIdleDone
	;; push args (0, $1000, EngineSectorStart), call ReadSectors, pop args
	;; TODO store $1000 sectors at location 0? doesn't make sense
	MOVEQ    #$0,D0
	MOVE.L   D0,-(A7)
	PEA      $1000
	MOVE.L   $10(A4),-(A7)
	JSR      ReadSectors(PC)
	LEA      $C(A7),A7
EngineIdleDone:	
	MOVEA.L  -$4(A6),A4
	UNLK     A6
	RTS

	dc.b	$8A,"EngineIdle",$00,$00,$00
	even

GetData:	
	;; FX args$00.l: EngineData
	;; FX args$04.l: data start offset from start of tile
	;; FX args$08.l: data length
	;; FX returns d0: -1 on error
	LINK     A6,#$0
	MOVEM.L  D5-D7/A4,-(A7)
	MOVE.L   Arg08Link(A6),D5
	MOVE.L   Arg04Link(A6),D6
	MOVEA.L  Arg00Link(A6),A4
	LEA      SubPlayStart(PC),A0
	;; get Ticks and save timeout as Ticks+601 (10/12 seconds for NTSC/PAL)
	MOVE.L   $4(A0),D7
	ADD.L    #601,D7
	BRA.S    GetDataLoopCondition
GetDataLoop:	
	;; push EngineData, call EngineIdle
	MOVE.L   A4,-(A7)
	JSR      EngineIdle(PC)
	LEA      SubPlayStart(PC),A0
	MOVEQ    #-$1,D0
	;; CDCReadDataNotPresent
	CMP.L    $38(A0),D0
	;; pop args from EngineIdle call
	ADDQ.W   #4,A7
	BNE.S    GetDataCDCHasNoError
	;; CDC read data not present occured
	MOVEQ    #-$1,D0
	BRA.S    GetDataEnd
GetDataCDCHasNoError:	
	;; CDDecoderNumberOfSectors
	LEA      SubPlayStart(PC),A0
	MOVEQ    #-$1,D0
	CMP.L    $10(A0),D0
	BNE.S    GetDataLoopCondition
	MOVEQ    #-$1,D0
	BRA.S    GetDataEnd
GetDataLoopCondition:	
	;; get data start + data length and compare to Label0060
	MOVE.L   D6,D0
	ADD.L    D5,D0
	CMP.L    $8(A4),D0
	BLE.S    GetDataWasRead
	;; check Ticks against timeout Ticks
	LEA      SubPlayStart(PC),A0
	CMP.L    $4(A0),D7
	BGT.S    GetDataLoop
GetDataWasRead:	
	;; check Ticks against timeout Ticks
	LEA      SubPlayStart(PC),A0
	CMP.L    $4(A0),D7
	BGT.S    GetDataNoTimeout
	;; timeout occured
	MOVEQ    #-$1,D0
	BRA.S    GetDataEnd
GetDataNoTimeout:	
	;; calculate (data start%EngineDataBufferSize) and save in d7
	MOVE.L   D6,D0
	MOVE.L   $4(A4),D1
	JSR      SignedDivideRemainderLongWordByLongWord(PC)
	MOVE.L   D0,D7
	;; calculate wrap around:
	;; (((data start%EngineDataBufferSize))+length-EngineDataBufferSize)
	ADD.L    D5,D0
	MOVE.L   D0,D6
	SUB.L    $4(A4),D6
	;; branch if no wrap around
	TST.L    D6
	BLE.S    GetDataNoWrapAround
	;; copy wrap around data from EngineDataBufferStart to
	;; EngineDataBufferStart+EngineDataBufferSize (since has extra room allocated here)
	;; push args for CopyData, call CopyData, pop args
	MOVE.L   D6,-(A7)
	MOVE.L   $4(A4),D0
	ADD.L    (A4),D0
	MOVE.L   D0,-(A7)
	MOVE.L   (A4),-(A7)
	JSR      CopyData(PC)
	LEA      $C(A7),A7
GetDataNoWrapAround:	
	;; EngineDataBufferStart
	ADD.L    (A4),D7
	MOVE.L   D7,D0
GetDataEnd:	
	MOVEM.L  -$10(A6),D5-D7/A4
	UNLK     A6
	RTS

	dc.b	$87,"GetData",$00,$00
	even

ReadData:	
	;; FX args$00.l: EngineData
	;; FX args$04.l: data start offset from start of tile
	;; FX args$08.l: data length
	LINK     A6,#$0
	MOVE.L   Arg08Link(A6),D0
	ADD.L    Arg04Link(A6),D0
	MOVEA.L  Arg00Link(A6),A0
	;; set EngineReadDataEnd to data start + data length
	MOVE.L   D0,$C(A0)
	UNLK     A6
	RTS

	dc.b	$88,"ReadData",$00,$00,$00
	even

AudioPlayPos:
	;; FX args$00.l: AudioData
	;; FX returns d0.l: audio play position (zero when silent)
	LINK     A6,#-$04
	MOVE.L   A4,-(A7)
	;; AudioData
	MOVEA.L  Arg00Link(A6),A4
	;; check Audio4kPlayedTicks for not playing
	MOVEQ    #-$1,D0
	CMP.L    (A4),D0
	BNE.S    AudioPlayPosNotSilent
	MOVEQ    #$0,D0
	BRA.S    AudioPlayPosDone
AudioPlayPosNotSilent:	
	;; get wave address
	JSR      GetWaveAddressCh1(PC)
	;; determine wave bank by shifting by 12 (divide by 4k)
	MOVEQ    #12,D1
	ASR.L    D1,D0
	MOVE.L   D0,LocalVar04(A6)
	;; calculate Audio4kPlayedTicks/8
	;; TODO why modulus by 8 when you can and with $7
	MOVE.L   (A4),D0
	MOVEQ    #$8,D1
	JSR      SignedDivideRemainderLongWordByLongWord(PC)
	EXT.W    D0
	EXT.L    D0
	MOVE.L   LocalVar04(A6),D1
	;; determine if on same 4k tick otherwise increment 4k ticks
	CMP.L    D1,D0
	BEQ.S    AudioPlayPosSetupReturnValue
	;; increment Audio4kPlayedTicks
	ADDQ.L   #1,(A4)
AudioPlayPosSetupReturnValue:	
	;; return Audio4kPlayedTicks in d0
	MOVE.L   (A4),D0
AudioPlayPosDone:	
	MOVEA.L  -$8(A6),A4
	UNLK     A6
	RTS

	dc.b	$8C,"AudioPlayPos",$00,$00,$00
	even

AudioInit:	
	;; FX args$00.l: AudioData
	LINK     A6,#$0
	MOVE.L   A4,-(A7)
	MOVEA.L  Arg00Link(A6),A4
	;; set Audio4kPlayedTicks to $FFFFFFFF (not playing)
	MOVEQ    #-$1,D0
	MOVE.L   D0,(A4)
	MOVEQ    #$0,D1
	;; clear Audio4kCopiedTicks
	MOVE.L   D1,$4(A4)
	;; clear AudioDataBufferStart
	MOVE.L   D1,$8(A4)
	;; clear AudioDataBufferSize and AudioBusyIndication
	MOVE.L   D1,$C(A4)
	;; setup PCM channels
	JSR      AudioInitSub(PC)
	MOVEA.L  -$4(A6),A4
	UNLK     A6
	RTS

	dc.b	$89,"AudioInit",$00,$00
	even

AudioIdle:
	;; FX args$00.l: AudioData
	LINK     A6,#$0
	MOVEM.L  D7/A4,-(A7)
	;; set A4 to AudioData
	MOVEA.L  Arg00Link(A6),A4
	;; push AudioData, call AudioPlayPos
	MOVE.L   A4,-(A7)
	JSR      AudioPlayPos(PC)
	MOVE.L   D0,D7
	;; test AudioDataBufferSize
	TST.L    $C(A4)
	;; pop args from AudioPlayPos
	ADDQ.W   #4,A7
	;; branch if AudioDataBufferSize is zero
	BEQ.S    AudioIdleDone
	;; branch if 8 <= ( Audio4kCopiedTicks - Audio4kPlayedTicks )
	MOVE.L   $4(A4),D0
	SUB.L    D7,D0
	MOVEQ    #8,D1
	CMP.L    D0,D1
	BLE.S    AudioIdleDone
	;; determine wave bank from (Audio4kCopiedTicks%8)
	;; TODO Audio4kCopiedTicks & $7 is better...
	MOVE.L   $4(A4),D0
	MOVEQ    #$8,D1
	JSR      SignedDivideRemainderLongWordByLongWord(PC)
	EXT.W    D0
	EXT.L    D0
	MOVE.L   D0,-(A7)
	;; AudioDataBufferStart
	MOVE.L   $8(A4),-(A7)
	JSR      Audio4kCopy(PC)
	;; increment Audio4kCopiedTicks
	ADDQ.L   #1,$4(A4)
	;; add 4k to AudioDataBufferStart
	ADDI.L   #$1000,$8(A4)
	;; subtract 4k from AudioDataBufferSize
	SUBI.L   #$1000,$C(A4)
	;; test AudioDataBufferSize
	TST.L    $C(A4)
	;; pop args to Audio4kCopy
	ADDQ.W   #8,A7
	;; branch if AudioDataBufferSize is greater or equal to zero
	BGE.S    AudioIdleDone
	;; set AudioDataBufferSize to zero
	MOVEQ    #$0,D0
	MOVE.L   D0,$C(A4)
AudioIdleDone:	
	MOVEM.L  -$8(A6),D7/A4
	UNLK     A6
	RTS

	dc.b	$89,"AudioIdle",$00,$00
	even

AudioFeed:	
	;; FX args$00.l: AudioData
	;; FX args$04.l: data start
	;; FX args$08.l: data length
	LINK     A6,#$0
	MOVE.L   A4,-(A7)
	;; AudioData
	MOVEA.L  Arg00Link(A6),A4
	;; push AudioData, call AudioIdle
	MOVE.L   A4,-(A7)
	JSR      AudioIdle(PC)
	;; save data start in AudioDataBufferStart
	MOVE.L   Arg04Link(A6),$8(A4)
	;; save data length AudioDataBufferSize
	MOVE.L   Arg08Link(A6),$C(A4)
	;; push AudioData, call AudioIdle
	MOVE.L   A4,-(A7)
	JSR      AudioIdle(PC)
	;; test Audio4kPlayedTicks
	MOVEQ    #-$1,D0
	CMP.L    (A4),D0
	;; pop args from AudioIdle and AudioIdle
	ADDQ.W   #8,A7
	;; test if Audio4kPlayedTicks indicates not playing
	;; so initial feed
	BNE.S    AudioFeedDone
	BRA.S    AudioFeedMoreCondition
AudioFeedMoreLoop:	
	;; push AudioData, call AudioIdle, pop args
	MOVE.L   A4,-(A7)
	JSR      AudioIdle(PC)
	ADDQ.W   #4,A7
AudioFeedMoreCondition:	
	;; branch if AudioDataBufferSize <= 0
	TST.L    $C(A4)
	BLE.S    AudioFeedStartPlaying
	;; loop if 8 > Audio4kCopiedTicks
	MOVEQ    #$8,D0
	CMP.L    $4(A4),D0
	BGT.S    AudioFeedMoreLoop
AudioFeedStartPlaying:	
	JSR      AudioStart(PC)
	;; set Audio4kPlayedTicks to zero (playing)
	MOVEQ    #$0,D0
	MOVE.L   D0,(A4)
AudioFeedDone:	
	MOVEA.L  -$4(A6),A4
	UNLK     A6
	RTS

	dc.b	$89,"AudioFeed",$00,$00
	even

AudioSilence:
	;; FX args$00.l: AudioData
	LINK     A6,#$0
	MOVEM.L  D3/D7/A3-A4,-(A7)
	;; AudioData
	MOVEA.L  Arg00Link(A6),A3
	;; check if Audio4kPlayedTicks is playing
	MOVEQ    #-$1,D0
	CMP.L    (A3),D0
	BNE.S    AudioSilenceDone
	;; push 4k, call mmalloc, save address
	PEA      $1000
	JSR      mmalloc(PC)
	MOVEA.L  D0,A4
	;; d7 is the long word index into A4
	MOVEQ    #$0,D7
	;; clear 1k long words (4k bytes)
	MOVE.L   #$400,D3
	;; pop mmalloc args
	ADDQ.W   #4,A7
	;; TODO an inefficient 4k clear
AudioSilenceZeroPCMLoop:	
	MOVE.L   D7,D0
	ASL.L    #2,D0
	MOVEQ    #$0,D1
	MOVE.L   D1,$0(A4,D0.L)
	MOVE.L   D7,D0
	ADDQ.L   #1,D7
	CMP.L    D7,D3
	BGT.S    AudioSilenceZeroPCMLoop
	;; clear 8 sets of 4k wave ram data
	MOVEQ    #$0,D7
AudioSilenceWaveRAMDataClearLoop:	
	;; push wave ram index and 4k block, call Audio4kCopy, pop args
	MOVE.L   D7,-(A7)
	MOVE.L   A4,-(A7)
	JSR      Audio4kCopy(PC)
	ADDQ.W   #8,A7
	;; loop if wave ram clear count is less than 8
	MOVE.L   D7,D0
	ADDQ.L   #1,D7
	MOVEQ    #$8,D0
	CMP.L    D7,D0
	BGT.S    AudioSilenceWaveRAMDataClearLoop
	;; free memory allocated by AudioSilence
	;; push allocated address, call ffree
	MOVE.L   A4,-(A7)
	JSR      ffree(PC)
	JSR      AudioStart(PC)
	;; set Audio4kPlayedTicks to zero (playing)
	MOVEQ    #$0,D0
	MOVE.L   D0,(A3)
	;; pop ffree args
	ADDQ.W   #4,A7
AudioSilenceDone:	
	MOVEM.L  -$10(A6),D3/D7/A3-A4
	UNLK     A6
	RTS

	dc.b	$8C,"AudioSilence",$00,$00,$00
	even

AudioStop:	
	;; FX args$00.l: AudioData
	LINK     A6,#$0
	JSR      AudioStopSub(PC)
	UNLK     A6
	RTS

	dc.b	$89,"AudioStop",$00,$00
	even

AudioTicks:	
	;; FX args$00.l: AudioData
	;; FX return d0.l: audio ticks
	LINK     A6,#-$4
	MOVE.L   A4,-(A7)
	;; AudioData
	MOVEA.L  Arg00Link(A6),A4
	;; push AudioData, call AudioPlayPos
	MOVE.L   A4,-(A7)
	JSR      AudioPlayPos(PC)
	;; calculate: ( Audio4kCopiedTicks << 12 ) | ( wave adrress & $FFF )
	MOVEQ    #12,D0
	MOVE.L   (A4),D1
	LSL.L    D0,D1
	MOVE.L   D1,LocalVar04(A6)
	JSR      GetWaveAddressCh1(PC)
	MOVE.L   #$FFF,D1
	AND.L    D0,D1
	MOVE.L   LocalVar04(A6),D0
	OR.L     D0,D1
	MOVE.L   D1,D0
	MOVEA.L  -$8(A6),A4
	UNLK     A6
	RTS

	dc.b	$8A,"AudioTicks",$00,$00,$00
	even

AudioTime:	
	;; FX args$00.l: AudioData
	;; FX args$04.l: FixedBaseRate
	LINK     A6,#$0
	;; calculate ((AudioTicks*BaseRate)+AudioTicksPerBaseRate/2)/AudioTicksPerBaseRate
	MOVE.L   Arg00Link(A6),-(A7)
	JSR      AudioTicks(PC)
	MOVE.L   D0,D1
	MOVE.L   Arg04Link(A6),D0
	JSR      UnsignedMultiplyLongWordByLongWord(PC)
	ADD.L    #(AudioTicksPerBaseRate/2),D0
	;; this is wrong, audio playback is really at 12.5MHz/384/2
	;; (so this value should really be 16276)
	MOVE.L   #AudioTicksPerBaseRate,D1
	JSR      SignedDivideLongWordByLongWord(PC)
	UNLK     A6
	RTS

	dc.b	$89,"AudioTime",$00,$00
	even

AudioBusy:
	;; FX args$00.l: AudioData
	;; FX returns d0.w (non-zero busy, zero not busy)
	LINK     A6,#$0
	MOVEA.L  Arg00Link(A6),A0
	;; Audio
	;; this should a long check on $C(A0)
	;; but this will never be a problem
	MOVE.W   $E(A0),D0
	UNLK     A6
	RTS

	dc.b	$89,"AudioBusy",$00,$00
	even

	dc.w	$202F,$0004,$222F,$0008

UnsignedMultiplyLongWordByLongWord:	
	;; FX args d0.l
	;; FX args d1.l
	MOVE.L   D0,-(A7)
	MULU.W   D1,D0
	MOVEA.L  D0,A0
	MOVE.L   D1,D0
	MULU.W   (A7)+,D1
	CLR.W    D0
	SWAP     D0
	BEQ.S    UnsignedMultiplyLongWordByLongWordSkipMultiplyIfZero
	MULU.W   (A7),D0
UnsignedMultiplyLongWordByLongWordSkipMultiplyIfZero:	
	ADDQ.L   #2,A7
	ADD.W    D1,D0
	SWAP     D0
	CLR.W    D0
	ADD.L    A0,D0
	RTS

	include	DIVIDE.ASM

	;; unused subroutine
	MOVE.W   -$6(A6),D0
	MOVE.W   (A7)+,D2
	RTS

	dc.w	$0000,$0000
